From d2ab2debb6504c395779070c0d78c7868284885e Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 9 Feb 2021 10:24:13 +0100 Subject: [PATCH 01/23] add expert logic --- app/controllers/application_controller.rb | 22 ++++++- app/controllers/experts/avis_controller.rb | 59 +++++++++++++++++++ app/controllers/experts/expert_controller.rb | 9 +++ .../instructeurs/procedures_controller.rb | 1 - app/models/expert.rb | 9 +++ app/views/experts/avis/_header.html.haml | 11 ++++ app/views/experts/avis/index.html.haml | 41 +++++++++++++ app/views/experts/avis/instruction.html.haml | 38 ++++++++++++ app/views/experts/avis/procedure.html.haml | 45 ++++++++++++++ .../experts/avis/shared/avis/_form.html.haml | 30 ++++++++++ app/views/experts/avis/show.html.haml | 5 ++ app/views/layouts/_account_dropdown.haml | 5 ++ .../views/layouts/_account_dropdown.en.yml | 1 + .../views/layouts/_account_dropdown.fr.yml | 1 + config/routes.rb | 37 ++++++++++++ 15 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 app/controllers/experts/avis_controller.rb create mode 100644 app/controllers/experts/expert_controller.rb create mode 100644 app/views/experts/avis/_header.html.haml create mode 100644 app/views/experts/avis/index.html.haml create mode 100644 app/views/experts/avis/instruction.html.haml create mode 100644 app/views/experts/avis/procedure.html.haml create mode 100644 app/views/experts/avis/shared/avis/_form.html.haml create mode 100644 app/views/experts/avis/show.html.haml diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6d0925e9a..5dcc24424 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -20,7 +20,7 @@ class ApplicationController < ActionController::Base before_action :setup_tracking before_action :set_locale - helper_method :multiple_devise_profile_connect?, :instructeur_signed_in?, :current_instructeur, + helper_method :multiple_devise_profile_connect?, :instructeur_signed_in?, :current_instructeur, :current_expert, :expert_signed_in?, :administrateur_signed_in?, :current_administrateur, :current_account def staging_authenticate @@ -32,7 +32,9 @@ class ApplicationController < ActionController::Base def multiple_devise_profile_connect? user_signed_in? && instructeur_signed_in? || instructeur_signed_in? && administrateur_signed_in? || - user_signed_in? && administrateur_signed_in? + instructeur_signed_in? && expert_signed_in? || + user_signed_in? && administrateur_signed_in? || + user_signed_in? && expert_signed_in? end def current_instructeur @@ -51,6 +53,14 @@ class ApplicationController < ActionController::Base current_administrateur.present? end + def current_expert + current_user&.expert + end + + def expert_signed_in? + current_expert.present? + end + def current_account { administrateur: current_administrateur, @@ -70,6 +80,8 @@ class ApplicationController < ActionController::Base def authenticate_logged_user! if instructeur_signed_in? authenticate_instructeur! + elsif expert_signed_in? + authenticate_expert! elsif administrateur_signed_in? authenticate_administrateur! else @@ -83,6 +95,12 @@ class ApplicationController < ActionController::Base end end + def authenticate_expert! + if !expert_signed_in? + redirect_to new_user_session_path + end + end + def authenticate_administrateur! if !administrateur_signed_in? redirect_to new_user_session_path diff --git a/app/controllers/experts/avis_controller.rb b/app/controllers/experts/avis_controller.rb new file mode 100644 index 000000000..4f0cc1306 --- /dev/null +++ b/app/controllers/experts/avis_controller.rb @@ -0,0 +1,59 @@ +module Experts + class AvisController < ExpertController + include CreateAvisConcern + + before_action :authenticate_expert!, except: [:sign_up, :create_instructeur] + before_action :check_if_avis_revoked, only: [:show] + before_action :redirect_if_no_sign_up_needed, only: [:sign_up] + before_action :check_avis_exists_and_email_belongs_to_avis, only: [:sign_up, :create_instructeur] + before_action :set_avis_and_dossier, only: [:show, :instruction, :messagerie, :create_commentaire, :update] + + A_DONNER_STATUS = 'a-donner' + DONNES_STATUS = 'donnes' + + def index + avis = current_expert.avis.includes(dossier: [groupe_instructeur: :procedure]) + @avis_by_procedure = avis.to_a.group_by(&:procedure) + end + + def procedure + @procedure = Procedure.find(params[:procedure_id]) + expert_avis = current_expert.avis.includes(:dossier).where(dossiers: { groupe_instructeur: GroupeInstructeur.where(procedure: @procedure.id) }) + @avis_a_donner = expert_avis.without_answer + @avis_donnes = expert_avis.with_answer + + @statut = params[:statut].presence || A_DONNER_STATUS + + @avis = case @statut + when A_DONNER_STATUS + @avis_a_donner + when DONNES_STATUS + @avis_donnes + end + + @avis = @avis.page([params[:page].to_i, 1].max) + end + + def show + end + + def instruction + @new_avis = Avis.new + end + + private + + def check_if_avis_revoked + avis = Avis.find(params[:id]) + if avis.revoked? + flash.alert = "Vous n'avez plus accès à ce dossier." + redirect_to url_for(root_path) + end + end + + def set_avis_and_dossier + @avis = Avis.find(params[:id]) + @dossier = @avis.dossier + end + end +end \ No newline at end of file diff --git a/app/controllers/experts/expert_controller.rb b/app/controllers/experts/expert_controller.rb new file mode 100644 index 000000000..adcbef5e0 --- /dev/null +++ b/app/controllers/experts/expert_controller.rb @@ -0,0 +1,9 @@ +module Experts + class ExpertController < ApplicationController + before_action :authenticate_expert! + + def nav_bar_profile + :expert + end + end +end diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 4aea5f5bd..e9eb91690 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -1,7 +1,6 @@ module Instructeurs class ProceduresController < InstructeurController before_action :ensure_ownership!, except: [:index] - before_action :redirect_to_avis_if_needed, only: [:index] ITEMS_PER_PAGE = 25 diff --git a/app/models/expert.rb b/app/models/expert.rb index 2e10b049e..ad65431f1 100644 --- a/app/models/expert.rb +++ b/app/models/expert.rb @@ -8,8 +8,17 @@ # class Expert < ApplicationRecord has_one :user + has_many :experts_procedures + has_many :avis, through: :experts_procedures + has_many :dossiers, through: :avis + + default_scope { eager_load(:user) } def email user.email end + + def self.by_email(email) + Expert.eager_load(:user).find_by(users: { email: email }) + end end diff --git a/app/views/experts/avis/_header.html.haml b/app/views/experts/avis/_header.html.haml new file mode 100644 index 000000000..803e317f4 --- /dev/null +++ b/app/views/experts/avis/_header.html.haml @@ -0,0 +1,11 @@ +.sub-header + .container + %ul.breadcrumbs + %li= link_to('Avis', expert_all_avis_path) + %li= link_to(dossier.procedure.libelle, procedure_expert_avis_index_path(avis.procedure)) + %li= link_to("Dossier nº #{dossier.id}", expert_avis_path(avis.procedure, avis)) + + %ul.tabs + = dynamic_tab_item('Demande', expert_avis_path(avis.procedure, avis)) + = dynamic_tab_item('Avis', instruction_expert_avis_path(avis.procedure, avis), notification: avis.answer.blank?) + = dynamic_tab_item('Messagerie', messagerie_expert_avis_path(avis.procedure, avis)) diff --git a/app/views/experts/avis/index.html.haml b/app/views/experts/avis/index.html.haml new file mode 100644 index 000000000..7b8838a3e --- /dev/null +++ b/app/views/experts/avis/index.html.haml @@ -0,0 +1,41 @@ +- content_for(:title, "Avis") + +.container + %h1.page-title Avis + + %ul.procedure-list + - @avis_by_procedure.each do |p, procedure_avis| + %li.procedure-item.flex.align-start + = link_to(procedure_instructeur_avis_index_path(p)) do + .flex + + .procedure-logo{ style: "background-image: url(#{p.logo_url})" } + + .procedure-details + %p.procedure-title + = procedure_libelle p + %ul.procedure-stats.flex + %li + %object + = link_to(procedure_instructeur_avis_index_path(p, statut: Instructeurs::AvisController::A_DONNER_STATUS)) do + - without_answer_count = procedure_avis.select { |a| a.answer.nil? }.size + - if without_answer_count > 0 + %span.notifications{ 'aria-label': "notifications" } + .stats-number + = without_answer_count + .stats-legend + avis à donner + %li + %object + = link_to(procedure_instructeur_avis_index_path(p, statut: Instructeurs::AvisController::DONNES_STATUS)) do + - with_answer_count = procedure_avis.select { |a| a.answer.present? }.size + .stats-number= with_answer_count + .stats-legend + = pluralize(with_answer_count, "avis donné") + + - if p.close? + .procedure-status + %span.label Close + - elsif p.depubliee? + .procedure-status + %span.label Dépubliée diff --git a/app/views/experts/avis/instruction.html.haml b/app/views/experts/avis/instruction.html.haml new file mode 100644 index 000000000..57e9d9e01 --- /dev/null +++ b/app/views/experts/avis/instruction.html.haml @@ -0,0 +1,38 @@ +- content_for(:title, "Avis · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") + += render partial: 'header', locals: { avis: @avis, dossier: @dossier } + +.container + %section.give-avis + %h1.tab-title Donner votre avis + %h2.claimant + Demandeur : + %span.email= @avis.claimant.email + %span.date Demande d'avis envoyée le #{l(@avis.created_at, format: '%d/%m/%y')} + %p.introduction= @avis.introduction + + - if @avis.introduction_file.attached? + = render partial: 'shared/attachment/show', locals: { attachment: @avis.introduction_file.attachment } + %br/ + + = form_for @avis, url: expert_avis_path(@avis.procedure, @avis), html: { class: 'form' } do |f| + = f.text_area :answer, rows: 3, placeholder: 'Votre avis', required: true + = text_upload_and_render f, @avis.piece_justificative_file + + .flex.justify-between.align-baseline + %p.confidentiel.flex + - if @avis.confidentiel? + %span.icon.lock + %span + Cet avis est confidentiel et n'est pas affiché aux autres experts consultés + - else + %span + Cet avis est partagé avec les autres experts + .send-wrapper + = f.submit 'Envoyer votre avis', class: 'button send' + + - if !@dossier.termine? && !feature_enabled_for?(:expert_not_allowed_to_invite, @avis.procedure) + = render partial: "instructeurs/shared/avis/form", locals: { url: avis_expert_avis_path(@avis.procedure, @avis), linked_dossiers: @dossier.linked_dossiers_for(current_expert), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } + + - if @dossier.avis_for(current_expert).present? + = render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis_for(current_expert), avis_seen_at: nil } diff --git a/app/views/experts/avis/procedure.html.haml b/app/views/experts/avis/procedure.html.haml new file mode 100644 index 000000000..fc79795d2 --- /dev/null +++ b/app/views/experts/avis/procedure.html.haml @@ -0,0 +1,45 @@ +- avis_statut = (@statut == Experts::AvisController::A_DONNER_STATUS) ? 'à donner' : 'rendus' +- content_for(:title, "Avis #{avis_statut}") + +#procedure-show + .sub-header + .container.flex + + .procedure-logo{ style: "background-image: url(#{@procedure.logo_url})", + role: 'img', 'aria-label': "logo de la démarche #{@procedure.libelle}" } + + .procedure-header + %h1= procedure_libelle @procedure + + %ul.tabs + = tab_item('avis à donner', + procedure_expert_avis_index_path(statut: Instructeurs::AvisController::A_DONNER_STATUS), + active: @statut == Instructeurs::AvisController::A_DONNER_STATUS, + badge: @avis_a_donner.count, + notification: @avis_a_donner.any?) + + = tab_item("avis #{'donné'.pluralize(@avis_donnes.count)}", + procedure_expert_avis_index_path(statut: Instructeurs::AvisController::DONNES_STATUS), + active: @statut == Instructeurs::AvisController::DONNES_STATUS, + badge: @avis_donnes.count) + +.container + - if @avis.present? + %table.table.dossiers-table.hoverable + %thead + %tr + %th.number-col Nº dossier + %th Demandeur + %th Démarche + %tbody + - @avis.each do |avis| + %tr + %td.number-col + = link_to(instructeur_avis_path(avis.procedure, avis), class: 'cell-link') do + %span.icon.folder + #{avis.dossier.id} + %td= link_to(avis.dossier.user.email, instructeur_avis_path(avis.procedure, avis), class: 'cell-link') + %td= link_to(avis.procedure.libelle, instructeur_avis_path(avis.procedure, avis), class: 'cell-link') + = paginate(@avis) + - else + %h2.empty-text Aucun avis diff --git a/app/views/experts/avis/shared/avis/_form.html.haml b/app/views/experts/avis/shared/avis/_form.html.haml new file mode 100644 index 000000000..a1bcf1fac --- /dev/null +++ b/app/views/experts/avis/shared/avis/_form.html.haml @@ -0,0 +1,30 @@ +%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 + + - 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. + + - 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' diff --git a/app/views/experts/avis/show.html.haml b/app/views/experts/avis/show.html.haml new file mode 100644 index 000000000..b2c53c6a8 --- /dev/null +++ b/app/views/experts/avis/show.html.haml @@ -0,0 +1,5 @@ +- content_for(:title, "Demande · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") + += render partial: 'header', locals: { avis: @avis, dossier: @dossier } + += render partial: 'shared/dossiers/demande', locals: { dossier: @dossier, demande_seen_at: nil, profile: 'instructeur' } diff --git a/app/views/layouts/_account_dropdown.haml b/app/views/layouts/_account_dropdown.haml index 1b1ab1627..e1c239238 100644 --- a/app/views/layouts/_account_dropdown.haml +++ b/app/views/layouts/_account_dropdown.haml @@ -22,6 +22,11 @@ = link_to instructeur_procedures_path, class: "menu-item menu-link" do = image_tag "icons/switch-profile.svg", alt: '' = t('go_instructor', scope: [:layouts]) + - if expert_signed_in? && nav_bar_profile != :expert + %li + = link_to expert_all_avis_path, class: "menu-item menu-link" do + = image_tag "icons/switch-profile.svg", alt: '' + = t('go_expert', scope: [:layouts]) - if administrateur_signed_in? && nav_bar_profile != :administrateur %li = link_to admin_procedures_path, class: "menu-item menu-link" do diff --git a/config/locales/views/layouts/_account_dropdown.en.yml b/config/locales/views/layouts/_account_dropdown.en.yml index e12456467..b3b3b16bf 100644 --- a/config/locales/views/layouts/_account_dropdown.en.yml +++ b/config/locales/views/layouts/_account_dropdown.en.yml @@ -3,6 +3,7 @@ en: go_superadmin: "Switch to super-admin" go_user: "Switch to user" go_instructor: "Switch to instructor" + go_expert: "Switch to expert" go_admin: "Switch to administrator" profile: "See my profile" logout: "Log out" diff --git a/config/locales/views/layouts/_account_dropdown.fr.yml b/config/locales/views/layouts/_account_dropdown.fr.yml index cc9fa9282..f0b566236 100644 --- a/config/locales/views/layouts/_account_dropdown.fr.yml +++ b/config/locales/views/layouts/_account_dropdown.fr.yml @@ -3,6 +3,7 @@ fr: go_superadmin: "Passer en super-admin" go_user: "Passer en usager" go_instructor: "Passer en instructeur" + go_expert: "Passer en expert" go_admin: "Passer en administrateur" profile: "Voir mon profil" logout: "Se déconnecter" diff --git a/config/routes.rb b/config/routes.rb index f3117b72b..b3e96b3f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -285,6 +285,43 @@ Rails.application.routes.draw do patch 'update_email' => 'profil#update_email' end + + + # + # Expert + # + scope module: 'experts', as: 'expert' do + get 'avis', to: 'avis#index', as: 'all_avis' + + # this redirections are ephemeral, to ensure that emails sent to experts before are still valid + # TODO : they will be removed in September, 2020 + get 'avis/:id', to: redirect('/procedures/old/avis/%{id}') + get 'avis/:id/sign_up/email/:email', to: redirect("/procedures/old/avis/%{id}/sign_up/email/%{email}"), constraints: { email: /.*/ } + + resources :procedures, only: [ :show], param: :procedure_id do + member do + resources :avis, only: [:show, :update] do + get '', action: 'procedure', on: :collection, as: :procedure + member do + get 'instruction' + get 'messagerie' + post 'commentaire' => 'avis#create_commentaire' + post 'avis' => 'avis#create_avis' + patch 'revoquer' + get 'revive' + get 'bilans_bdf' + + get 'sign_up/email/:email' => 'avis#sign_up', constraints: { email: /.*/ }, as: 'sign_up' + post 'sign_up/email/:email' => 'avis#create_instructeur', constraints: { email: /.*/ } + end + end + end + end + end + + + + # # Instructeur # From d47fde3fcbbdb04829357d567fcbf0001868ef87 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:30:16 +0100 Subject: [PATCH 02/23] add polymorphic relation to claimant on avis table --- app/models/avis.rb | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/app/models/avis.rb b/app/models/avis.rb index 3d3cbf598..72409077b 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -9,6 +9,7 @@ # email :string # introduction :text # revoked_at :datetime +# tmp_expert_migrated :boolean default(FALSE) # created_at :datetime not null # updated_at :datetime not null # claimant_id :integer not null @@ -23,11 +24,11 @@ class Avis < ApplicationRecord belongs_to :instructeur, optional: true belongs_to :experts_procedure, optional: true belongs_to :claimant, class_name: 'Instructeur', optional: false - has_one :procedure, through: :dossier has_one_attached :piece_justificative_file has_one_attached :introduction_file has_one :expert, through: :experts_procedure + has_one :procedure, through: :experts_procedure validates :piece_justificative_file, content_type: AUTHORIZED_CONTENT_TYPES, @@ -45,7 +46,7 @@ class Avis < ApplicationRecord before_validation -> { sanitize_email(:email) } before_create :try_to_assign_instructeur - default_scope { joins(:dossier) } +default_scope { joins(:dossier) } scope :with_answer, -> { where.not(answer: nil) } scope :without_answer, -> { where(answer: nil) } scope :for_dossier, -> (dossier_id) { where(dossier_id: dossier_id) } @@ -57,6 +58,27 @@ class Avis < ApplicationRecord attr_accessor :emails attr_accessor :invite_linked_dossiers + def claimant + claimant_id = read_attribute(:claimant_id) + claimant_type = read_attribute(:claimant_type) + if claimant_type == 'Instructeur' || !tmp_expert_migrated + Instructeur.find(claimant_id) + else + Expert.find(claimant_id).user.expert + end + end + + def claimant=(claimant) + self.claimant_id = claimant.id + + if claimant.is_a? Instructeur + self.claimant_type = 'Instructeur' + else + self.claimant_type = 'Expert' + self.tmp_expert_migrated = true + end + end + def email_to_display instructeur&.email || email end @@ -102,7 +124,7 @@ class Avis < ApplicationRecord destroy! end end - + private def try_to_assign_instructeur From a71011637143161b836de2c01f3b2aaa96d5b267 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:32:23 +0100 Subject: [PATCH 03/23] add expert profile to api --- app/graphql/api/v2/schema.rb | 2 +- app/graphql/types/avis_type.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/graphql/api/v2/schema.rb b/app/graphql/api/v2/schema.rb index e077a26f3..b783a5945 100644 --- a/app/graphql/api/v2/schema.rb +++ b/app/graphql/api/v2/schema.rb @@ -26,7 +26,7 @@ class API::V2::Schema < GraphQL::Schema Types::DossierType when Commentaire Types::MessageType - when Instructeur, User + when Instructeur, User, Expert Types::ProfileType when Individual Types::PersonnePhysiqueType diff --git a/app/graphql/types/avis_type.rb b/app/graphql/types/avis_type.rb index d31217299..de29a8e19 100644 --- a/app/graphql/types/avis_type.rb +++ b/app/graphql/types/avis_type.rb @@ -12,6 +12,6 @@ module Types ] field :instructeur, Types::ProfileType, null: false, method: :claimant - field :expert, Types::ProfileType, null: true, method: :instructeur + field :expert, Types::ProfileType, null: true end end From 54d414b3b7e44218ef6590d67a2ea72254dd365b Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:37:28 +0100 Subject: [PATCH 04/23] add expert to export --- app/models/avis.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/avis.rb b/app/models/avis.rb index 72409077b..ac3b51dc7 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -99,7 +99,7 @@ default_scope { joins(:dossier) } ['Créé le', :created_at], ['Répondu le', :updated_at], ['Instructeur', claimant&.email], - ['Expert', instructeur&.email] + ['Expert', expert&.email] ] end From 568b1c4e53784193b3b75093e400c4dbb1f823f1 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:42:19 +0100 Subject: [PATCH 05/23] remove instructeur unused layout --- app/views/instructeurs/avis/sign_up.html.haml | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 app/views/instructeurs/avis/sign_up.html.haml diff --git a/app/views/instructeurs/avis/sign_up.html.haml b/app/views/instructeurs/avis/sign_up.html.haml deleted file mode 100644 index 476ca4c49..000000000 --- a/app/views/instructeurs/avis/sign_up.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -.two-columns.avis-sign-up - .columns-container - .column.left - %p.description= @dossier.procedure.libelle - %p.dossier Dossier nº #{@dossier.id} - .column - = form_for(User.new, url: { controller: "instructeurs/avis", action: :create_instructeur }, method: :post, html: { class: "form" }) do |f| - %h1 Créez-vous un compte - - = f.label :email, "Email" - = f.email_field :email, value: @email, disabled: true - - = f.label :password, "Mot de passe" - = f.password_field :password, required: true, placeholder: "#{PASSWORD_MIN_LENGTH} caractères minimum" - - = f.submit "Créer un compte", class: "button large primary expand" From e8207535ea4d62bb6ad604ddc6d5a079c253f919 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:44:42 +0100 Subject: [PATCH 06/23] modify instructeur views --- app/views/instructeurs/shared/avis/_list.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/instructeurs/shared/avis/_list.html.haml b/app/views/instructeurs/shared/avis/_list.html.haml index ff3cd426c..f40d05d18 100644 --- a/app/views/instructeurs/shared/avis/_list.html.haml +++ b/app/views/instructeurs/shared/avis/_list.html.haml @@ -22,24 +22,24 @@ %span.icon.bubble.avis-icon .width-100 %h2.instructeur - = (avis.email_to_display == current_instructeur.email) ? 'Vous' : avis.email_to_display + = (avis.expert.email == current_instructeur.email) ? 'Vous' : avis.expert.email - if avis.answer.present? - if avis.revoked? %span.waiting{ class: highlight_if_unseen_class(avis_seen_at, avis.revoked_at) } = t('demande_revoquee_le', scope: 'views.shared.avis', date: l(avis.revoked_at, format: '%d/%m/%y à %H:%M')) - else - if avis.revokable_by?(current_instructeur) - %span.waiting= link_to(t('revoke', scope: 'helpers.label'), revoquer_instructeur_avis_path(avis.procedure, avis), data: { confirm: t('revoke', scope: 'helpers.confirmation', email: avis.email_to_display) }, method: :patch) + %span.waiting= link_to(t('revoke', scope: 'helpers.label'), revoquer_instructeur_avis_path(avis.procedure, avis), data: { confirm: t('revoke', scope: 'helpers.confirmation', email: avis.expert.email) }, method: :patch) %span.date{ class: highlight_if_unseen_class(avis_seen_at, avis.updated_at) } = t('reponse_donnee_le', scope: 'views.shared.avis', date: l(avis.updated_at, format: '%d/%m/%y à %H:%M')) - else %span.waiting = t('en_attente', scope: 'views.shared.avis') | - %span.waiting= link_to(t('revive', scope: 'helpers.label'), revive_instructeur_avis_path(avis.procedure, avis), data: { confirm: t('revive', scope: 'helpers.confirmation', email: avis.email_to_display) }) + %span.waiting= link_to(t('revive', scope: 'helpers.label'), revive_instructeur_avis_path(avis.procedure, avis), data: { confirm: t('revive', scope: 'helpers.confirmation', email: avis.expert.email) }) - if avis.revokable_by?(current_instructeur) | - = link_to(t('revoke', scope: 'helpers.label'), revoquer_instructeur_avis_path(avis.procedure, avis), data: { confirm: t('revoke', scope: 'helpers.confirmation', email: avis.email_to_display) }, method: :patch) + = link_to(t('revoke', scope: 'helpers.label'), revoquer_instructeur_avis_path(avis.procedure, avis), data: { confirm: t('revoke', scope: 'helpers.confirmation', email: avis.expert.email) }, method: :patch) - if avis.piece_justificative_file.attached? = render partial: 'shared/attachment/show', locals: { attachment: avis.piece_justificative_file.attachment } .answer-body From c55e4d0d98df96cbcc871c6b5fe9de6083f0b9ce Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:53:09 +0100 Subject: [PATCH 07/23] remove unused instructeur logic --- .../instructeurs/avis_controller.rb | 172 +----------------- .../instructeurs/dossiers_controller.rb | 4 +- app/models/avis.rb | 13 +- 3 files changed, 7 insertions(+), 182 deletions(-) diff --git a/app/controllers/instructeurs/avis_controller.rb b/app/controllers/instructeurs/avis_controller.rb index f44da58cf..28bcd74b1 100644 --- a/app/controllers/instructeurs/avis_controller.rb +++ b/app/controllers/instructeurs/avis_controller.rb @@ -2,131 +2,14 @@ module Instructeurs class AvisController < InstructeurController include CreateAvisConcern - before_action :authenticate_instructeur!, except: [:sign_up, :create_instructeur] - before_action :check_if_avis_revoked, only: [:show] - before_action :redirect_if_no_sign_up_needed, only: [:sign_up] - before_action :check_avis_exists_and_email_belongs_to_avis, only: [:sign_up, :create_instructeur] - before_action :set_avis_and_dossier, only: [:show, :instruction, :messagerie, :create_commentaire, :update] - + before_action :authenticate_instructeur! A_DONNER_STATUS = 'a-donner' DONNES_STATUS = 'donnes' - def index - avis = current_instructeur.avis.includes(:procedure) - @avis_by_procedure = avis.to_a.group_by(&:procedure) - end - - def procedure - @procedure = Procedure.find(params[:procedure_id]) - instructeur_avis = current_instructeur.avis.includes(:dossier).where(dossiers: { revision: @procedure.revisions }) - @avis_a_donner = instructeur_avis.without_answer - @avis_donnes = instructeur_avis.with_answer - - @statut = params[:statut].presence || A_DONNER_STATUS - - @avis = case @statut - when A_DONNER_STATUS - @avis_a_donner - when DONNES_STATUS - @avis_donnes - end - - @avis = @avis.page([params[:page].to_i, 1].max) - end - - def show - end - - def instruction - @new_avis = Avis.new - end - - def update - if @avis.update(avis_params) - flash.notice = 'Votre réponse est enregistrée.' - @avis.dossier.update!(last_avis_updated_at: Time.zone.now) - redirect_to instruction_instructeur_avis_path(@avis.procedure, @avis) - else - flash.now.alert = @avis.errors.full_messages - @new_avis = Avis.new - render :instruction - end - end - - def messagerie - @commentaire = Commentaire.new - end - - def create_commentaire - @commentaire = CommentaireService.build(current_instructeur, avis.dossier, commentaire_params) - - if @commentaire.save - @commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now) - flash.notice = "Message envoyé" - redirect_to messagerie_instructeur_avis_path(avis.procedure, avis) - else - flash.alert = @commentaire.errors.full_messages - render :messagerie - end - end - - def create_avis - @procedure = Procedure.find(params[:procedure_id]) - if !@procedure.feature_enabled?(:expert_not_allowed_to_invite) - @new_avis = create_avis_from_params(avis.dossier, avis.confidentiel) - - if @new_avis.nil? - redirect_to instruction_instructeur_avis_path(avis.procedure, avis) - else - set_avis_and_dossier - render :instruction - end - else - flash.alert = "Cette démarche ne vous permet pas de demander un avis externe" - redirect_to instruction_instructeur_avis_path(avis.procedure, avis) - end - end - - def bilans_bdf - if avis.dossier.etablissement&.entreprise_bilans_bdf.present? - extension = params[:format] - render extension.to_sym => avis.dossier.etablissement.entreprise_bilans_bdf_to_sheet(extension) - else - redirect_to instructeur_avis_path(avis) - end - end - - def sign_up - @email = params[:email] - @dossier = Avis.includes(:dossier).find(params[:id]).dossier - - render - end - - def create_instructeur - procedure_id = params[:procedure_id] - avis_id = params[:id] - email = params[:email] - password = params[:user][:password] - - # Not perfect because the password will not be changed if the user already exists - user = User.create_or_promote_to_instructeur(email, password) - - if user.valid? - sign_in(user) - - Avis.link_avis_to_instructeur(user.instructeur) - redirect_to url_for(instructeur_all_avis_path) - else - flash[:alert] = user.errors.full_messages - redirect_to url_for(sign_up_instructeur_avis_path(procedure_id, avis_id, email)) - end - end - def revoquer avis = Avis.find(params[:id]) if avis.revoke_by!(current_instructeur) - flash.notice = "#{avis.email_to_display} ne peut plus donner son avis sur ce dossier." + flash.notice = "#{avis.expert.email} ne peut plus donner son avis sur ce dossier." redirect_back(fallback_location: avis_instructeur_dossier_path(avis.procedure, avis.dossier)) end end @@ -136,60 +19,13 @@ module Instructeurs if avis.revivable_by?(current_instructeur) if avis.answer.blank? AvisMailer.avis_invitation(avis).deliver_later - flash.notice = "Un mail de relance a été envoyé à #{avis.email_to_display}" + flash.notice = "Un mail de relance a été envoyé à #{avis.expert.email}" redirect_back(fallback_location: avis_instructeur_dossier_path(avis.procedure, avis.dossier)) else - flash.alert = "#{avis.email} a déjà donné son avis" + flash.alert = "#{avis.expert.email} a déjà donné son avis" redirect_back(fallback_location: avis_instructeur_dossier_path(avis.procedure, avis.dossier)) end end end - - private - - def set_avis_and_dossier - @avis = avis - @dossier = avis.dossier - end - - def redirect_if_no_sign_up_needed - avis = Avis.find(params[:id]) - - if current_instructeur.present? - # a instructeur is authenticated ... lets see if it can view the dossier - - redirect_to instructeur_avis_url(avis.procedure, avis) - elsif avis.instructeur&.email == params[:email] - # the avis instructeur has already signed up and it sould sign in - - redirect_to new_user_session_url - end - end - - def check_if_avis_revoked - avis = Avis.find(params[:id]) - if avis.revoked? - flash.alert = "Vous n'avez plus accès à ce dossier." - redirect_to url_for(root_path) - end - end - - def check_avis_exists_and_email_belongs_to_avis - if !Avis.avis_exists_and_email_belongs_to_avis?(params[:id], params[:email]) - redirect_to url_for(root_path) - end - end - - def avis - current_instructeur.avis.includes(dossier: [:avis, :commentaires]).find(params[:id]) - end - - def avis_params - params.require(:avis).permit(:answer, :piece_justificative_file) - end - - def commentaire_params - params.require(:commentaire).permit(:body, :piece_jointe) - end end end diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index 078d37c95..0324f03e6 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -66,7 +66,7 @@ module Instructeurs @following_instructeurs_emails = dossier.followers_instructeurs.map(&:email) previous_followers = dossier.previous_followers_instructeurs - dossier.followers_instructeurs @previous_following_instructeurs_emails = previous_followers.map(&:email) - @avis_emails = dossier.avis.includes(:instructeur).map(&:email_to_display) + @avis_emails = dossier.experts.map(&:email) @invites_emails = dossier.invites.map(&:email) @potential_recipients = dossier.groupe_instructeur.instructeurs.reject { |g| g == current_instructeur } end @@ -181,7 +181,7 @@ module Instructeurs end def create_avis - @avis = create_avis_from_params(dossier) + @avis = create_avis_from_params(dossier, current_instructeur) if @avis.nil? redirect_to avis_instructeur_dossier_path(procedure, dossier) diff --git a/app/models/avis.rb b/app/models/avis.rb index ac3b51dc7..9ee5d37ba 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -44,9 +44,8 @@ class Avis < ApplicationRecord validates :introduction_file, size: { less_than: 20.megabytes } before_validation -> { sanitize_email(:email) } - before_create :try_to_assign_instructeur -default_scope { joins(:dossier) } + default_scope { joins(:dossier) } scope :with_answer, -> { where.not(answer: nil) } scope :without_answer, -> { where(answer: nil) } scope :for_dossier, -> (dossier_id) { where(dossier_id: dossier_id) } @@ -124,14 +123,4 @@ default_scope { joins(:dossier) } destroy! end end - - private - - def try_to_assign_instructeur - instructeur = Instructeur.by_email(email) - if instructeur - self.instructeur = instructeur - self.email = nil - end - end end From e79b1204e000ebb9707ad531afdb0e6abf721445 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 09:58:21 +0100 Subject: [PATCH 08/23] change avis invitation mailer --- app/mailers/avis_mailer.rb | 2 +- app/views/avis_mailer/avis_invitation.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/mailers/avis_mailer.rb b/app/mailers/avis_mailer.rb index 0f1b887a2..7b616afee 100644 --- a/app/mailers/avis_mailer.rb +++ b/app/mailers/avis_mailer.rb @@ -6,7 +6,7 @@ class AvisMailer < ApplicationMailer def avis_invitation(avis) @avis = avis - email = @avis.instructeur&.email || @avis.email + email = @avis.expert&.email subject = "Donnez votre avis sur le dossier nº #{@avis.dossier.id} (#{@avis.dossier.procedure.libelle})" mail(to: email, subject: subject) diff --git a/app/views/avis_mailer/avis_invitation.html.haml b/app/views/avis_mailer/avis_invitation.html.haml index 5914665fe..52f08054d 100644 --- a/app/views/avis_mailer/avis_invitation.html.haml +++ b/app/views/avis_mailer/avis_invitation.html.haml @@ -1,5 +1,5 @@ - content_for(:title, 'Invitation à donner votre avis') -- avis_link = @avis.instructeur.present? ? instructeur_avis_url(@avis.procedure, @avis) : sign_up_instructeur_avis_url(@avis.procedure, @avis.id, @avis.email) +- avis_link = @avis.expert.user.active?.present? ? expert_avis_url(@avis.procedure, @avis) : sign_up_expert_avis_url(@avis.procedure, @avis.id, @avis.expert.email) - content_for(:footer) do Merci de ne pas répondre à cet email. Donnez votre avis @@ -23,7 +23,7 @@ %p{ style: "padding: 8px; color: #333333; background-color: #EEEEEE; font-size: 14px;" } = @avis.introduction -- if @avis.instructeur.present? +- if @avis.expert.user.active?.present? %p = round_button("Donner votre avis", avis_link, :primary) - else From 5519ee84174490725098f6ccae029621e3c39d10 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 10:05:19 +0100 Subject: [PATCH 09/23] Expert Layout --- app/views/experts/avis/instruction.html.haml | 8 ++-- app/views/experts/avis/messagerie.html.haml | 5 +++ app/views/experts/avis/show.html.haml | 2 +- app/views/experts/avis/sign_up.html.haml | 16 ++++++++ app/views/experts/shared/avis/_form.html.haml | 30 +++++++++++++++ app/views/experts/shared/avis/_list.html.haml | 38 +++++++++++++++++++ 6 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 app/views/experts/avis/messagerie.html.haml create mode 100644 app/views/experts/avis/sign_up.html.haml create mode 100644 app/views/experts/shared/avis/_form.html.haml create mode 100644 app/views/experts/shared/avis/_list.html.haml diff --git a/app/views/experts/avis/instruction.html.haml b/app/views/experts/avis/instruction.html.haml index 57e9d9e01..d20025c5c 100644 --- a/app/views/experts/avis/instruction.html.haml +++ b/app/views/experts/avis/instruction.html.haml @@ -31,8 +31,8 @@ .send-wrapper = f.submit 'Envoyer votre avis', class: 'button send' - - if !@dossier.termine? && !feature_enabled_for?(:expert_not_allowed_to_invite, @avis.procedure) - = render partial: "instructeurs/shared/avis/form", locals: { url: avis_expert_avis_path(@avis.procedure, @avis), linked_dossiers: @dossier.linked_dossiers_for(current_expert), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } + - if !@dossier.termine? && !@avis.procedure.feature_enabled?(:expert_not_allowed_to_invite) + = render partial: "experts/shared/avis/form", locals: { url: avis_expert_avis_path(@avis.procedure, @avis), linked_dossiers: @dossier.linked_dossiers_for(current_expert), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } - - if @dossier.avis_for(current_expert).present? - = render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis_for(current_expert), avis_seen_at: nil } + - if @dossier.avis_for_expert(current_expert).present? + = render partial: 'experts/shared/avis/list', locals: { avis: @dossier.avis_for_expert(current_expert), avis_seen_at: nil } diff --git a/app/views/experts/avis/messagerie.html.haml b/app/views/experts/avis/messagerie.html.haml new file mode 100644 index 000000000..b3d21b225 --- /dev/null +++ b/app/views/experts/avis/messagerie.html.haml @@ -0,0 +1,5 @@ +- content_for(:title, "Messagerie · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") + += render partial: 'header', locals: { avis: @avis, dossier: @dossier } + += render partial: "shared/dossiers/messagerie", locals: { dossier: @dossier, connected_user: current_expert, messagerie_seen_at: nil, new_commentaire: @commentaire, form_url: commentaire_expert_avis_path(@avis) } diff --git a/app/views/experts/avis/show.html.haml b/app/views/experts/avis/show.html.haml index b2c53c6a8..3704bedac 100644 --- a/app/views/experts/avis/show.html.haml +++ b/app/views/experts/avis/show.html.haml @@ -2,4 +2,4 @@ = render partial: 'header', locals: { avis: @avis, dossier: @dossier } -= render partial: 'shared/dossiers/demande', locals: { dossier: @dossier, demande_seen_at: nil, profile: 'instructeur' } += render partial: 'shared/dossiers/demande', locals: { dossier: @dossier, demande_seen_at: nil, profile: 'expert' } diff --git a/app/views/experts/avis/sign_up.html.haml b/app/views/experts/avis/sign_up.html.haml new file mode 100644 index 000000000..f93ed115f --- /dev/null +++ b/app/views/experts/avis/sign_up.html.haml @@ -0,0 +1,16 @@ +.two-columns.avis-sign-up + .columns-container + .column.left + %p.description= @dossier.procedure.libelle + %p.dossier Dossier nº #{@dossier.id} + .column + = form_for(User.new, url: { controller: "experts/avis", action: :update_expert }, method: :post, html: { class: "form" }) do |f| + %h1 Créez-vous un compte + + = f.label :email, "Email" + = f.email_field :email, value: @email, disabled: true + + = f.label :password, "Mot de passe" + = f.password_field :password, required: true, placeholder: "#{PASSWORD_MIN_LENGTH} caractères minimum" + + = f.submit "Créer un compte", class: "button large primary expand" diff --git a/app/views/experts/shared/avis/_form.html.haml b/app/views/experts/shared/avis/_form.html.haml new file mode 100644 index 000000000..a1bcf1fac --- /dev/null +++ b/app/views/experts/shared/avis/_form.html.haml @@ -0,0 +1,30 @@ +%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 + + - 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. + + - 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' diff --git a/app/views/experts/shared/avis/_list.html.haml b/app/views/experts/shared/avis/_list.html.haml new file mode 100644 index 000000000..f9acf92cd --- /dev/null +++ b/app/views/experts/shared/avis/_list.html.haml @@ -0,0 +1,38 @@ +%section.list-avis + %h1.tab-title + Avis des invités + %span.count= avis.count + + %ul + - avis.each do |avis| + %li.one-avis.flex.align-start + .width-100 + %h2.claimant + = "#{t('claimant', scope: 'activerecord.attributes.avis')} :" + %span.email= (avis.claimant.email == current_expert.email) ? 'Vous' : avis.claimant.email + - if avis.confidentiel? + %span.confidentiel + = t('confidentiel', scope: 'activerecord.attributes.avis') + %span.icon.lock{ title: t('confidentiel', scope: 'helpers.hint') } + %span.date{ class: highlight_if_unseen_class(avis_seen_at, avis.created_at) } + = t('demande_envoyee_le', scope: 'views.shared.avis', date: l(avis.created_at, format: '%d/%m/%y à %H:%M')) + %p= avis.introduction + + .answer.flex.align-start + %span.icon.bubble.avis-icon + .width-100 + %h2.instructeur + = (avis.expert.email == current_expert.email) ? 'Vous' : avis.expert.email + - if avis.answer.present? + - if avis.revoked? + %span.waiting{ class: highlight_if_unseen_class(avis_seen_at, avis.revoked_at) } + = t('demande_revoquee_le', scope: 'views.shared.avis', date: l(avis.revoked_at, format: '%d/%m/%y à %H:%M')) + %span.date{ class: highlight_if_unseen_class(avis_seen_at, avis.updated_at) } + = t('reponse_donnee_le', scope: 'views.shared.avis', date: l(avis.updated_at, format: '%d/%m/%y à %H:%M')) + - else + %span.waiting + = t('en_attente', scope: 'views.shared.avis') + - if avis.piece_justificative_file.attached? + = render partial: 'shared/attachment/show', locals: { attachment: avis.piece_justificative_file.attachment } + .answer-body + = simple_format(avis.answer) From 38740d1b5b9c473d3a6227287f370d7d0ecaa426 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 10:10:24 +0100 Subject: [PATCH 10/23] modify experts avis controllers, concern and serializer --- .../concerns/create_avis_concern.rb | 13 +- app/controllers/experts/avis_controller.rb | 112 +++++++++++++++++- app/models/dossier.rb | 23 +++- app/serializers/avis_serializer.rb | 5 +- 4 files changed, 136 insertions(+), 17 deletions(-) diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb index 3b711733e..3b42ca786 100644 --- a/app/controllers/concerns/create_avis_concern.rb +++ b/app/controllers/concerns/create_avis_concern.rb @@ -3,9 +3,8 @@ module CreateAvisConcern private - def create_avis_from_params(dossier, confidentiel = false) + def create_avis_from_params(dossier, instructeur_or_expert, confidentiel = false) confidentiel ||= create_avis_params[:confidentiel] - # Because of a limitation of the email_field rails helper, # the :emails parameter is a 1-element array. # Hence the call to first @@ -14,7 +13,7 @@ module CreateAvisConcern allowed_dossiers = [dossier] if create_avis_params[:invite_linked_dossiers].present? - allowed_dossiers += dossier.linked_dossiers_for(current_instructeur) + allowed_dossiers += dossier.linked_dossiers_for(instructeur_or_expert) end create_results = Avis.create( @@ -26,8 +25,7 @@ module CreateAvisConcern email: email, introduction: create_avis_params[:introduction], introduction_file: create_avis_params[:introduction_file], - claimant: current_instructeur, - claimant_type: current_instructeur.dossiers.present? ? 'Instructeur' : 'Expert', + claimant: instructeur_or_expert, dossier: dossier, confidentiel: confidentiel, experts_procedure: experts_procedure @@ -43,10 +41,11 @@ module CreateAvisConcern sent_emails_addresses = [] persisted.each do |avis| avis.dossier.demander_un_avis!(avis) - if avis.dossier == dossier AvisMailer.avis_invitation(avis).deliver_later - sent_emails_addresses << avis.email_to_display + sent_emails_addresses << avis.expert.email + # the email format is already verified, we update value to nil + avis.update_column(:email, nil) end end flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses.uniq.join(", ")}" diff --git a/app/controllers/experts/avis_controller.rb b/app/controllers/experts/avis_controller.rb index 4f0cc1306..7d205db3e 100644 --- a/app/controllers/experts/avis_controller.rb +++ b/app/controllers/experts/avis_controller.rb @@ -2,10 +2,9 @@ module Experts class AvisController < ExpertController include CreateAvisConcern - before_action :authenticate_expert!, except: [:sign_up, :create_instructeur] + before_action :authenticate_expert!, except: [:sign_up, :update_expert] before_action :check_if_avis_revoked, only: [:show] before_action :redirect_if_no_sign_up_needed, only: [:sign_up] - before_action :check_avis_exists_and_email_belongs_to_avis, only: [:sign_up, :create_instructeur] before_action :set_avis_and_dossier, only: [:show, :instruction, :messagerie, :create_commentaire, :update] A_DONNER_STATUS = 'a-donner' @@ -41,8 +40,107 @@ module Experts @new_avis = Avis.new end + def create_avis + @procedure = Procedure.find(params[:procedure_id]) + if !@procedure.feature_enabled?(:expert_not_allowed_to_invite) + @new_avis = create_avis_from_params(avis.dossier, current_expert, avis.confidentiel) + + if @new_avis.nil? + redirect_to instruction_expert_avis_path(avis.procedure, avis) + else + set_avis_and_dossier + render :instruction + end + else + flash.alert = "Cette démarche ne vous permet pas de demander un avis externe" + redirect_to instruction_expert_avis_path(avis.procedure, avis) + end + end + + def update + if @avis.update(avis_params) + flash.notice = 'Votre réponse est enregistrée.' + @avis.dossier.update!(last_avis_updated_at: Time.zone.now) + redirect_to instruction_expert_avis_path(@avis.procedure, @avis) + else + flash.now.alert = @avis.errors.full_messages + @new_avis = Avis.new + render :instruction + end + end + + def sign_up + @email = params[:email] + @dossier = Avis.includes(:dossier).find(params[:id]).dossier + + render + end + + def update_expert + procedure_id = params[:procedure_id] + avis_id = params[:id] + email = params[:email] + password = params[:user][:password] + + # Not perfect because the password will not be changed if the user already exists + user = User.create_or_promote_to_expert(email, password) + + if user.valid? + sign_in(user) + + redirect_to url_for(expert_all_avis_path) + else + flash[:alert] = user.errors.full_messages + redirect_to url_for(sign_up_expert_avis_path(procedure_id, avis_id, email)) + end + end + + def messagerie + @commentaire = Commentaire.new + end + + def create_commentaire + @commentaire = CommentaireService.build(current_expert, avis.dossier, commentaire_params) + + if @commentaire.save + @commentaire.dossier.update!(last_commentaire_updated_at: Time.zone.now) + flash.notice = "Message envoyé" + redirect_to messagerie_expert_avis_path(avis.procedure, avis) + else + flash.alert = @commentaire.errors.full_messages + render :messagerie + end + end + + def bilans_bdf + if avis.dossier.etablissement&.entreprise_bilans_bdf.present? + extension = params[:format] + render extension.to_sym => avis.dossier.etablissement.entreprise_bilans_bdf_to_sheet(extension) + else + redirect_to instructeur_avis_path(avis) + end + end + private + def redirect_if_no_sign_up_needed + avis = Avis.find(params[:id]) + + if current_expert.present? + # an expert is authenticated ... lets see if it can view the dossier + + redirect_to expert_avis_url(avis.procedure, avis) + + elsif avis.expert&.email == params[:email] && avis.expert.user.active?.present? + + redirect_to new_user_session_url + end + end + + def avis + current_expert.avis.includes(dossier: [:avis, :commentaires]).find(params[:id]) + end + def check_if_avis_revoked avis = Avis.find(params[:id]) if avis.revoked? @@ -55,5 +153,13 @@ module Experts @avis = Avis.find(params[:id]) @dossier = @avis.dossier end + + def avis_params + params.require(:avis).permit(:answer, :piece_justificative_file) + end + + def commentaire_params + params.require(:commentaire).permit(:body, :piece_jointe) + end end -end \ No newline at end of file +end diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 7c1039b00..8b4d4bbee 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -74,6 +74,7 @@ class Dossier < ApplicationRecord has_many :followers_instructeurs, through: :follows, source: :instructeur has_many :previous_followers_instructeurs, -> { distinct }, through: :previous_follows, source: :instructeur has_many :avis, inverse_of: :dossier, dependent: :destroy + has_many :experts, through: :avis has_many :traitements, -> { order(:processed_at) }, inverse_of: :dossier, dependent: :destroy has_many :dossier_operation_logs, -> { order(:created_at) }, inverse_of: :dossier @@ -475,18 +476,32 @@ class Dossier < ApplicationRecord parts.join end - def avis_for(instructeur) + def avis_for_instructeur(instructeur) if instructeur.dossiers.include?(self) avis.order(created_at: :asc) else avis .where(confidentiel: false) - .or(avis.where(claimant: instructeur)) + .or(avis.where(claimant_id: instructeur.id, claimant_type: 'Instructeur')) .or(avis.where(instructeur: instructeur)) .order(created_at: :asc) end end + def avis_for_expert(expert) + if expert.dossiers.include?(self) + avis.order(created_at: :asc) + else + instructeur = expert.user.instructeur.id if expert.user.instructeur + avis + .where(confidentiel: false) + .or(avis.where(claimant_id: expert.id, claimant_type: 'Expert', tmp_expert_migrated: true)) + .or(avis.where(claimant_id: instructeur, claimant_type: 'Instructeur', tmp_expert_migrated: false)) + .or(avis.where(expert: expert)) + .order(created_at: :asc) + end + end + def owner_name if etablissement.present? etablissement.entreprise_raison_sociale @@ -811,9 +826,9 @@ class Dossier < ApplicationRecord && PiecesJustificativesService.pieces_justificatives_total_size(self) < Dossier::TAILLE_MAX_ZIP end - def linked_dossiers_for(instructeur) + def linked_dossiers_for(instructeur_or_expert) dossier_ids = champs.filter(&:dossier_link?).map(&:value).compact - (instructeur.dossiers.where(id: dossier_ids) + instructeur.dossiers_from_avis.where(id: dossier_ids)).uniq + instructeur_or_expert.dossiers.where(id: dossier_ids) end def hash_for_deletion_mail diff --git a/app/serializers/avis_serializer.rb b/app/serializers/avis_serializer.rb index 7811c1152..0b40903e3 100644 --- a/app/serializers/avis_serializer.rb +++ b/app/serializers/avis_serializer.rb @@ -1,12 +1,11 @@ class AvisSerializer < ActiveModel::Serializer - attributes :email, - :answer, + attributes :answer, :introduction, :created_at, :answered_at def email - object.email_to_display + object.expert.email end def created_at From c7392d8140a5981f72c256efafe241969923ca6b Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 10:10:42 +0100 Subject: [PATCH 11/23] changes routes --- config/routes.rb | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index b3e96b3f0..049667606 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -285,8 +285,6 @@ Rails.application.routes.draw do patch 'update_email' => 'profil#update_email' end - - # # Expert # @@ -298,7 +296,7 @@ Rails.application.routes.draw do get 'avis/:id', to: redirect('/procedures/old/avis/%{id}') get 'avis/:id/sign_up/email/:email', to: redirect("/procedures/old/avis/%{id}/sign_up/email/%{email}"), constraints: { email: /.*/ } - resources :procedures, only: [ :show], param: :procedure_id do + resources :procedures, only: [], param: :procedure_id do member do resources :avis, only: [:show, :update] do get '', action: 'procedure', on: :collection, as: :procedure @@ -307,20 +305,15 @@ Rails.application.routes.draw do get 'messagerie' post 'commentaire' => 'avis#create_commentaire' post 'avis' => 'avis#create_avis' - patch 'revoquer' - get 'revive' get 'bilans_bdf' get 'sign_up/email/:email' => 'avis#sign_up', constraints: { email: /.*/ }, as: 'sign_up' - post 'sign_up/email/:email' => 'avis#create_instructeur', constraints: { email: /.*/ } + post 'sign_up/email/:email' => 'avis#update_expert', constraints: { email: /.*/ } end end end end end - - - # # Instructeur @@ -346,16 +339,12 @@ Rails.application.routes.draw do resources :avis, only: [:show, :update] do get '', action: 'procedure', on: :collection, as: :procedure member do - get 'instruction' get 'messagerie' post 'commentaire' => 'avis#create_commentaire' post 'avis' => 'avis#create_avis' patch 'revoquer' get 'revive' get 'bilans_bdf' - - get 'sign_up/email/:email' => 'avis#sign_up', constraints: { email: /.*/ }, as: 'sign_up' - post 'sign_up/email/:email' => 'avis#create_instructeur', constraints: { email: /.*/ } end end From 2325023b1a69487e532ba7967a0e24593847593d Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 25 Feb 2021 10:12:24 +0100 Subject: [PATCH 12/23] after party task to fill claimant_type on avis table --- ...claimant_id_for_experts_on_avis_table.rake | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 lib/tasks/deployment/20210311085419_backfill_claimant_id_for_experts_on_avis_table.rake diff --git a/lib/tasks/deployment/20210311085419_backfill_claimant_id_for_experts_on_avis_table.rake b/lib/tasks/deployment/20210311085419_backfill_claimant_id_for_experts_on_avis_table.rake new file mode 100644 index 000000000..bc11c3a3e --- /dev/null +++ b/lib/tasks/deployment/20210311085419_backfill_claimant_id_for_experts_on_avis_table.rake @@ -0,0 +1,31 @@ +namespace :after_party do + desc 'Deployment task: backfill_claimant_id_for_experts_on_avis_table' + task backfill_claimant_id_for_experts_on_avis_table: :environment do + puts "Running deploy task 'backfill_claimant_id_for_experts_on_avis_table'" + + avis_experts_claimant = Avis.where(claimant_type: 'Expert', tmp_expert_migrated: false) + progress = ProgressReport.new(avis_experts_claimant.count) + + avis_experts_claimant.find_each do |avis| + claimant_instructeur = Instructeur.find(avis.claimant_id) + if claimant_instructeur.user + claimant_expert = claimant_instructeur.user.expert + if !claimant_expert + User.create_or_promote_to_expert(claimant_instructeur.user.email, SecureRandom.hex) + claimant_expert = claimant_instructeur.reload.user.expert + ExpertsProcedure.find_or_create_by(procedure: avis.procedure, expert: claimant_expert) + end + avis.update_columns(claimant_id: claimant_expert.id, tmp_expert_migrated: true) + else + # Avis associated to an Instructeur with no user are bad data: delete it + avis.destroy! + end + progress.inc + end + progress.finish + # Update task as completed. If you remove the line below, the task will + # run with every deploy (or every time you call after_party:run). + AfterParty::TaskRecord + .create version: AfterParty::TaskRecorder.new(__FILE__).timestamp + end +end From e7945594cf35c63c5aa166045fc6a77bb99cfa8a Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Sun, 28 Feb 2021 22:19:22 +0100 Subject: [PATCH 13/23] eager_load for expert --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 7de9ff077..2eb549880 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -53,7 +53,7 @@ class User < ApplicationRecord accepts_nested_attributes_for :france_connect_information - default_scope { eager_load(:instructeur, :administrateur) } + default_scope { eager_load(:instructeur, :administrateur, :expert) } before_validation -> { sanitize_email(:email) } From 328c2a8e3c0a32d47c6b7159b73af607a9a1a508 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Sun, 28 Feb 2021 22:19:49 +0100 Subject: [PATCH 14/23] change email to display for expert --- app/models/avis.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/avis.rb b/app/models/avis.rb index 9ee5d37ba..2c6b68a99 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -79,7 +79,7 @@ class Avis < ApplicationRecord end def email_to_display - instructeur&.email || email + expert&.email end def self.link_avis_to_instructeur(instructeur) From 81f5a5254b9f1239e17d84235d2803d5a8534ab0 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Sun, 28 Feb 2021 22:20:24 +0100 Subject: [PATCH 15/23] tests --- app/helpers/dossier_link_helper.rb | 10 +- .../experts/avis_controller_spec.rb | 354 ++++++++++++++ .../instructeurs/avis_controller_spec.rb | 444 +----------------- .../instructeurs/dossiers_controller_spec.rb | 18 +- .../procedures_controller_spec.rb | 41 -- spec/controllers/invites_controller_spec.rb | 5 +- spec/features/experts/expert_spec.rb | 77 +++ spec/features/instructeurs/expert_spec.rb | 92 +--- spec/helpers/dossier_link_helper_spec.rb | 7 +- spec/mailers/avis_mailer_spec.rb | 13 +- spec/models/avis_spec.rb | 83 +--- spec/models/dossier_spec.rb | 49 +- spec/models/experts_procedure_spec.rb | 3 +- .../avis/instruction.html.haml.spec.rb} | 11 +- .../shared/avis/list.html.haml_spec.rb | 7 +- .../shared/dossiers/_champs.html.haml_spec.rb | 4 - 16 files changed, 544 insertions(+), 674 deletions(-) create mode 100644 spec/controllers/experts/avis_controller_spec.rb create mode 100644 spec/features/experts/expert_spec.rb rename spec/views/{instructeur/avis/instruction.html.haml_spec.rb => experts/avis/instruction.html.haml.spec.rb} (63%) diff --git a/app/helpers/dossier_link_helper.rb b/app/helpers/dossier_link_helper.rb index 8c356e925..4dacd9960 100644 --- a/app/helpers/dossier_link_helper.rb +++ b/app/helpers/dossier_link_helper.rb @@ -3,11 +3,11 @@ module DossierLinkHelper if user.is_a?(Instructeur) if user.groupe_instructeurs.include?(dossier.groupe_instructeur) instructeur_dossier_path(dossier.procedure, dossier) - else - avis = dossier.avis.find_by(instructeur: user) - if avis.present? - instructeur_avis_path(avis.procedure, avis) - end + end + elsif user.is_a?(Expert) + avis = dossier.avis.find_by(expert: user.user) + if avis.present? + expert_avis_path(avis.procedure, avis) end elsif user.owns_or_invite?(dossier) dossier_path(dossier) diff --git a/spec/controllers/experts/avis_controller_spec.rb b/spec/controllers/experts/avis_controller_spec.rb new file mode 100644 index 000000000..493d5ddc5 --- /dev/null +++ b/spec/controllers/experts/avis_controller_spec.rb @@ -0,0 +1,354 @@ +describe Experts::AvisController, type: :controller do + context 'with an expert signed in' do + render_views + + let(:now) { Time.zone.parse('01/02/2345') } + let(:instructeur) { create(:instructeur) } + let(:claimant) { create(:expert) } + let(:expert) { create(:expert) } + let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } + let(:another_procedure) { create(:procedure, :published, instructeurs: [instructeur]) } + let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + let!(:avis_without_answer) { Avis.create(dossier: dossier, claimant: claimant, experts_procedure: experts_procedure) } + let!(:avis_with_answer) { Avis.create(dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, answer: 'yop') } + + before do + sign_in(expert.user) + end + + describe '#index' do + before { get :index } + it { expect(response).to have_http_status(:success) } + it { expect(assigns(:avis_by_procedure).flatten).to include(procedure) } + it { expect(assigns(:avis_by_procedure).flatten).not_to include(another_procedure) } + end + + describe '#procedure' do + before { get :procedure, params: { procedure_id: procedure.id } } + + it { expect(response).to have_http_status(:success) } + it { expect(assigns(:avis_a_donner)).to match([avis_without_answer]) } + it { expect(assigns(:avis_donnes)).to match([avis_with_answer]) } + it { expect(assigns(:statut)).to eq('a-donner') } + + context 'with a statut equal to donnes' do + before { get :procedure, params: { statut: 'donnes', procedure_id: procedure.id } } + + it { expect(assigns(:statut)).to eq('donnes') } + end + end + + describe '#bilans_bdf' do + before { get :bilans_bdf, params: { id: avis_without_answer.id, procedure_id: procedure.id } } + + it { expect(response).to redirect_to(instructeur_avis_path(avis_without_answer)) } + end + + describe '#show' do + subject { get :show, params: { id: avis_with_answer.id, procedure_id: procedure.id } } + + context 'with a valid avis' do + before { subject } + + it { expect(response).to have_http_status(:success) } + it { expect(assigns(:avis)).to eq(avis_with_answer) } + it { expect(assigns(:dossier)).to eq(dossier) } + end + + context 'with a revoked avis' do + it "refuse l'accès au dossier" do + avis_with_answer.update!(revoked_at: Time.zone.now) + subject + expect(flash.alert).to eq("Vous n'avez plus accès à ce dossier.") + expect(response).to redirect_to(root_path) + end + end + end + + describe '#instruction' do + before { get :instruction, params: { id: avis_without_answer.id, procedure_id: procedure.id } } + + it { expect(response).to have_http_status(:success) } + it { expect(assigns(:avis)).to eq(avis_without_answer) } + it { expect(assigns(:dossier)).to eq(dossier) } + end + + describe '#messagerie' do + before { get :messagerie, params: { id: avis_without_answer.id, procedure_id: procedure.id } } + + it { expect(response).to have_http_status(:success) } + it { expect(assigns(:avis)).to eq(avis_without_answer) } + it { expect(assigns(:dossier)).to eq(dossier) } + end + + describe '#update' do + context 'without attachment' do + before do + Timecop.freeze(now) + patch :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer' } } + avis_without_answer.reload + end + after { Timecop.return } + + it 'should be ok' do + expect(response).to redirect_to(instruction_expert_avis_path(avis_without_answer.procedure, avis_without_answer)) + expect(avis_without_answer.answer).to eq('answer') + expect(avis_without_answer.piece_justificative_file).to_not be_attached + expect(dossier.reload.last_avis_updated_at).to eq(now) + expect(flash.notice).to eq('Votre réponse est enregistrée.') + end + end + + context 'with attachment' do + include ActiveJob::TestHelper + let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } + + before do + expect(ClamavService).to receive(:safe_file?).and_return(true) + post :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer', piece_justificative_file: file } } + perform_enqueued_jobs + avis_without_answer.reload + end + + it 'should be ok' do + expect(response).to redirect_to(instruction_expert_avis_path(avis_without_answer.procedure, avis_without_answer)) + expect(avis_without_answer.answer).to eq('answer') + expect(avis_without_answer.piece_justificative_file).to be_attached + expect(flash.notice).to eq('Votre réponse est enregistrée.') + end + end + end + + describe '#create_commentaire' do + let(:file) { nil } + let(:scan_result) { true } + let(:now) { Time.zone.parse("14/07/1789") } + + subject { post :create_commentaire, params: { id: avis_without_answer.id, procedure_id: procedure.id, commentaire: { body: 'commentaire body', piece_jointe: file } } } + + before do + allow(ClamavService).to receive(:safe_file?).and_return(scan_result) + Timecop.freeze(now) + end + + after { Timecop.return } + + it do + subject + + expect(response).to redirect_to(messagerie_expert_avis_path(avis_without_answer.procedure, avis_without_answer)) + expect(dossier.commentaires.map(&:body)).to match(['commentaire body']) + expect(dossier.reload.last_commentaire_updated_at).to eq(now) + end + + context "with a file" do + let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } + + it do + subject + expect(Commentaire.last.piece_jointe.filename).to eq("piece_justificative_0.pdf") + end + + it { expect { subject }.to change(Commentaire, :count).by(1) } + end + end + + describe '#expert_cannot_invite_another_expert' do + let!(:previous_avis) { Avis.create(dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, confidentiel: previous_avis_confidentiel) } + let(:previous_avis_confidentiel) { false } + let(:asked_confidentiel) { false } + let(:intro) { 'introduction' } + let(:emails) { ["toto@totomail.com"] } + let(:invite_linked_dossiers) { nil } + + before do + Flipper.enable_actor(:expert_not_allowed_to_invite, procedure) + post :create_avis, params: { id: previous_avis.id, procedure_id: procedure.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers, introduction_file: @introduction_file } } + end + + context 'when the expert cannot invite another expert' do + let(:asked_confidentiel) { false } + it { expect(flash.alert).to eq("Cette démarche ne vous permet pas de demander un avis externe") } + it { expect(response).to redirect_to(instruction_expert_avis_path(procedure, previous_avis)) } + end + end + + describe '#create_avis' do + let!(:previous_avis) { Avis.create(dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, confidentiel: previous_avis_confidentiel) } + let(:emails) { ['a@b.com'] } + let(:intro) { 'introduction' } + let(:created_avis) { Avis.last } + let!(:old_avis_count) { Avis.count } + let(:invite_linked_dossiers) { nil } + + before do + Timecop.freeze(now) + @introduction_file = fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') + post :create_avis, params: { id: previous_avis.id, procedure_id: procedure.id, avis: { emails: emails, introduction: intro, experts_procedure: experts_procedure, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers, introduction_file: @introduction_file } } + created_avis.reload + end + + after { Timecop.return } + + context 'when an invalid email' do + let(:previous_avis_confidentiel) { false } + let(:asked_confidentiel) { false } + let(:emails) { ["toto.fr"] } + + it { expect(response).to render_template :instruction } + it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } + it { expect(Avis.last).to eq(previous_avis) } + it { expect(dossier.last_avis_updated_at).to eq(nil) } + end + + context 'ask review with attachment' do + let(:previous_avis_confidentiel) { false } + let(:asked_confidentiel) { false } + 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") } + it { expect(created_avis.dossier.reload.last_avis_updated_at).to eq(now) } + it { expect(flash.notice).to eq("Une demande d'avis a été envoyée à toto@totomail.com") } + end + + context 'with multiple emails' do + let(:asked_confidentiel) { false } + let(:previous_avis_confidentiel) { false } + 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"]) } + it { expect(flash.notice).to eq("Une demande d'avis a été envoyée à titi@titimail.com") } + it { expect(Avis.count).to eq(old_avis_count + 1) } + end + + context 'when the previous avis is public' do + let(:previous_avis_confidentiel) { false } + + context 'when the user asked for a public avis' do + let(:asked_confidentiel) { false } + + it { expect(created_avis.confidentiel).to be(false) } + it { expect(created_avis.introduction).to eq(intro) } + it { expect(created_avis.dossier).to eq(previous_avis.dossier) } + it { expect(created_avis.claimant).to eq(expert) } + it { expect(response).to redirect_to(instruction_expert_avis_path(previous_avis.procedure, previous_avis)) } + end + + context 'when the user asked for a confidentiel avis' do + let(:asked_confidentiel) { true } + + it { expect(created_avis.confidentiel).to be(true) } + end + end + + context 'when the preivous avis is confidentiel' do + let(:previous_avis_confidentiel) { true } + + context 'when the user asked for a public avis' do + let(:asked_confidentiel) { false } + + 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) } + + context 'when the expert doesn’t share linked dossiers' do + let(:invite_linked_dossiers) { false } + + it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' 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.dossier).to eq(dossier) + end + end + + context 'when the expert also shares the linked dossiers' do + context 'and the expert can access the linked dossiers' do + let(:created_avis) { Avis.create(dossier: dossier, claimant: claimant, email: "toto3@gmail.com") } + let(:linked_dossier) { Dossier.find_by(id: dossier.reload.champs.filter(&:dossier_link?).map(&:value).compact) } + let(:linked_avis) { Avis.create(dossier: linked_dossier, claimant: claimant) } + let(:invite_linked_dossiers) { true } + + it 'sends one avis for the main dossier' do + expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com") + expect(created_avis.dossier).to eq(dossier) + end + + it 'sends another avis for the linked dossiers' do + expect(Avis.count).to eq(old_avis_count + 2) + expect(linked_avis.dossier).to eq(linked_dossier) + end + end + + context 'but the expert can’t access the linked dossier' do + it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' 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.dossier).to eq(dossier) + end + end + end + end + end + end + + context 'without an expert signed in' do + describe '#sign_up' do + let(:invited_email) { 'invited@avis.com' } + let(:claimant) { create(:instructeur) } + let(:expert) { create(:expert) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + let(:dossier) { create(:dossier) } + let(:procedure) { dossier.procedure } + let!(:avis) { create(:avis, experts_procedure: experts_procedure, claimant: claimant, dossier: dossier) } + let(:invitations_email) { true } + + context 'when the expert has already signed up and belongs to the invitation' do + let!(:avis) { create(:avis, dossier: dossier, experts_procedure: experts_procedure, claimant: claimant) } + + context 'when the expert is authenticated' do + before do + sign_in(expert.user) + expert.user.update(last_sign_in_at: Time.zone.now) + expert.user.reload + get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: avis.expert.email } + end + + it { is_expected.to redirect_to expert_avis_url(avis.procedure, avis) } + end + + context 'when the expert is not authenticated' do + before do + sign_in(expert.user) + expert.user.update(last_sign_in_at: Time.zone.now) + expert.user.reload + sign_out(expert.user) + get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: avis.expert.email } + end + + it { is_expected.to redirect_to new_user_session_url } + end + end + + context 'when the expert has already signed up / is authenticated and does not belong to the invitation' do + let(:expert) { create(:expert) } + let!(:avis) { create(:avis, email: invited_email, dossier: dossier, experts_procedure: experts_procedure) } + + before do + sign_in(expert.user) + get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: avis.expert.email } + end + + # redirected to dossier but then the instructeur gonna be banished ! + it { is_expected.to redirect_to expert_avis_url(avis.procedure, avis) } + end + end + end +end diff --git a/spec/controllers/instructeurs/avis_controller_spec.rb b/spec/controllers/instructeurs/avis_controller_spec.rb index cce22cd01..7b5b5b7e2 100644 --- a/spec/controllers/instructeurs/avis_controller_spec.rb +++ b/spec/controllers/instructeurs/avis_controller_spec.rb @@ -3,318 +3,28 @@ describe Instructeurs::AvisController, type: :controller do render_views let(:now) { Time.zone.parse('01/02/2345') } + let(:expert) { create(:expert) } let(:claimant) { create(:instructeur) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } let(:instructeur) { create(:instructeur) } - let(:procedure) { create(:procedure, :published, instructeurs: [claimant]) } - let(:another_procedure) { create(:procedure, :published, instructeurs: [claimant]) } + let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } let(:dossier) { create(:dossier, :en_construction, procedure: procedure) } - let!(:avis_without_answer) { Avis.create(dossier: dossier, claimant: claimant, instructeur: instructeur) } - let!(:avis_with_answer) { Avis.create(dossier: dossier, claimant: claimant, instructeur: instructeur, answer: 'yop') } + let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } + let!(:avis_without_answer) { Avis.create(dossier: dossier, claimant: claimant, experts_procedure: experts_procedure) } before { sign_in(instructeur.user) } - describe '#index' do - before { get :index } - - it { expect(response).to have_http_status(:success) } - it { expect(assigns(:avis_by_procedure).flatten).to include(procedure) } - it { expect(assigns(:avis_by_procedure).flatten).not_to include(another_procedure) } - end - - describe '#procedure' do - before { get :procedure, params: { procedure_id: procedure.id } } - - it { expect(response).to have_http_status(:success) } - it { expect(assigns(:avis_a_donner)).to match([avis_without_answer]) } - it { expect(assigns(:avis_donnes)).to match([avis_with_answer]) } - it { expect(assigns(:statut)).to eq('a-donner') } - - context 'with a statut equal to donnes' do - before { get :procedure, params: { statut: 'donnes', procedure_id: procedure.id } } - - it { expect(assigns(:statut)).to eq('donnes') } - end - end - - describe '#show' do - subject { get :show, params: { id: avis_with_answer.id, procedure_id: procedure.id } } - - context 'with a valid avis' do - before { subject } - - it { expect(response).to have_http_status(:success) } - it { expect(assigns(:avis)).to eq(avis_with_answer) } - it { expect(assigns(:dossier)).to eq(dossier) } - end - - context 'with a revoked avis' do - it "refuse l'accès au dossier" do - avis_with_answer.update!(revoked_at: Time.zone.now) - subject - expect(flash.alert).to eq("Vous n'avez plus accès à ce dossier.") - expect(response).to redirect_to(root_path) - end - end - end - - describe '#instruction' do - before { get :instruction, params: { id: avis_without_answer.id, procedure_id: procedure.id } } - - it { expect(response).to have_http_status(:success) } - it { expect(assigns(:avis)).to eq(avis_without_answer) } - it { expect(assigns(:dossier)).to eq(dossier) } - end - - describe '#messagerie' do - before { get :messagerie, params: { id: avis_without_answer.id, procedure_id: procedure.id } } - - it { expect(response).to have_http_status(:success) } - it { expect(assigns(:avis)).to eq(avis_without_answer) } - it { expect(assigns(:dossier)).to eq(dossier) } - end - - describe '#bilans_bdf' do - before { get :bilans_bdf, params: { id: avis_without_answer.id, procedure_id: procedure.id } } - - it { expect(response).to redirect_to(instructeur_avis_path(avis_without_answer)) } - end - - describe '#update' do - context 'without attachment' do - before do - Timecop.freeze(now) - patch :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer' } } - avis_without_answer.reload - end - after { Timecop.return } - - it 'should be ok' do - expect(response).to redirect_to(instruction_instructeur_avis_path(avis_without_answer.procedure, avis_without_answer)) - expect(avis_without_answer.answer).to eq('answer') - expect(avis_without_answer.piece_justificative_file).to_not be_attached - expect(dossier.reload.last_avis_updated_at).to eq(now) - expect(flash.notice).to eq('Votre réponse est enregistrée.') - end - end - - context 'with attachment' do - let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } - - before do - post :update, params: { id: avis_without_answer.id, procedure_id: procedure.id, avis: { answer: 'answer', piece_justificative_file: file } } - avis_without_answer.reload - end - - it 'should be ok' do - expect(response).to redirect_to(instruction_instructeur_avis_path(avis_without_answer.procedure, avis_without_answer)) - expect(avis_without_answer.answer).to eq('answer') - expect(avis_without_answer.piece_justificative_file).to be_attached - expect(flash.notice).to eq('Votre réponse est enregistrée.') - end - end - end - - describe '#create_commentaire' do - let(:file) { nil } - let(:scan_result) { true } - let(:now) { Time.zone.parse("14/07/1789") } - - subject { post :create_commentaire, params: { id: avis_without_answer.id, procedure_id: procedure.id, commentaire: { body: 'commentaire body', piece_jointe: file } } } - - before do - Timecop.freeze(now) - end - - after { Timecop.return } - - it do - subject - - expect(response).to redirect_to(messagerie_instructeur_avis_path(avis_without_answer.procedure, avis_without_answer)) - expect(dossier.commentaires.map(&:body)).to match(['commentaire body']) - expect(dossier.reload.last_commentaire_updated_at).to eq(now) - end - - context "with a file" do - let(:file) { fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') } - - it do - subject - expect(Commentaire.last.piece_jointe.filename).to eq("piece_justificative_0.pdf") - end - - it { expect { subject }.to change(Commentaire, :count).by(1) } - end - end - - describe '#expert_cannot_invite_another_expert' do - let!(:previous_avis) { Avis.create(dossier: dossier, claimant: claimant, instructeur: instructeur, confidentiel: previous_avis_confidentiel) } - let(:previous_avis_confidentiel) { false } - let(:asked_confidentiel) { false } - let(:intro) { 'introduction' } - let(:emails) { ["toto@totomail.com"] } - let(:invite_linked_dossiers) { nil } - - before do - Flipper.enable_actor(:expert_not_allowed_to_invite, procedure) - post :create_avis, params: { id: previous_avis.id, procedure_id: procedure.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers, introduction_file: @introduction_file } } - end - - context 'when the expert cannot invite another expert' do - let(:asked_confidentiel) { false } - it { expect(flash.alert).to eq("Cette démarche ne vous permet pas de demander un avis externe") } - it { expect(response).to redirect_to(instruction_instructeur_avis_path(procedure, previous_avis)) } - end - end - - describe '#create_avis' do - let!(:previous_avis) { Avis.create(dossier: dossier, claimant: claimant, instructeur: instructeur, confidentiel: previous_avis_confidentiel) } - let(:emails) { ['a@b.com'] } - let(:intro) { 'introduction' } - let(:created_avis) { Avis.last } - let!(:old_avis_count) { Avis.count } - let(:invite_linked_dossiers) { nil } - - before do - Timecop.freeze(now) - @introduction_file = fixture_file_upload('spec/fixtures/files/piece_justificative_0.pdf', 'application/pdf') - post :create_avis, params: { id: previous_avis.id, procedure_id: procedure.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers, introduction_file: @introduction_file } } - created_avis.reload - end - after { Timecop.return } - - context 'when an invalid email' do - let(:previous_avis_confidentiel) { false } - let(:asked_confidentiel) { false } - let(:emails) { ["toto.fr"] } - - it { expect(response).to render_template :instruction } - it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } - it { expect(Avis.last).to eq(previous_avis) } - it { expect(dossier.last_avis_updated_at).to eq(nil) } - end - - context 'ask review with attachment' do - let(:previous_avis_confidentiel) { false } - let(:asked_confidentiel) { false } - 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") } - it { expect(created_avis.dossier.reload.last_avis_updated_at).to eq(now) } - it { expect(flash.notice).to eq("Une demande d'avis a été envoyée à toto@totomail.com") } - end - - context 'with multiple emails' do - let(:asked_confidentiel) { false } - let(:previous_avis_confidentiel) { false } - 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"]) } - it { expect(flash.notice).to eq("Une demande d'avis a été envoyée à titi@titimail.com") } - it { expect(Avis.count).to eq(old_avis_count + 1) } - it { expect(created_avis.email).to eq("titi@titimail.com") } - end - - context 'when the previous avis is public' do - let(:previous_avis_confidentiel) { false } - - context 'when the user asked for a public avis' do - let(:asked_confidentiel) { false } - - it { expect(created_avis.confidentiel).to be(false) } - it { expect(created_avis.email).to eq(emails.last) } - it { expect(created_avis.introduction).to eq(intro) } - it { expect(created_avis.dossier).to eq(previous_avis.dossier) } - it { expect(created_avis.claimant).to eq(instructeur) } - it { expect(response).to redirect_to(instruction_instructeur_avis_path(previous_avis.procedure, previous_avis)) } - end - - context 'when the user asked for a confidentiel avis' do - let(:asked_confidentiel) { true } - - it { expect(created_avis.confidentiel).to be(true) } - end - end - - context 'when the preivous avis is confidentiel' do - let(:previous_avis_confidentiel) { true } - - context 'when the user asked for a public avis' do - let(:asked_confidentiel) { false } - - 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) } - - context 'when the expert doesn’t share linked dossiers' do - let(:invite_linked_dossiers) { false } - - it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' 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 - end - - context 'when the expert also shares the linked dossiers' do - let(:invite_linked_dossiers) { true } - - context 'and the expert can access the linked dossiers' do - let(:created_avis) { Avis.last(2).first } - let(:linked_avis) { Avis.last } - let(:linked_dossier) { Dossier.find_by(id: dossier.reload.champs.filter(&:dossier_link?).map(&:value).compact) } - let(:invite_linked_dossiers) do - instructeur.assign_to_procedure(linked_dossier.procedure) - true - end - - it 'sends one avis for the main dossier' do - expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com") - expect(created_avis.email).to eq("a@b.com") - expect(created_avis.dossier).to eq(dossier) - end - - it 'sends another avis for the linked dossiers' do - expect(Avis.count).to eq(old_avis_count + 2) - expect(linked_avis.dossier).to eq(linked_dossier) - end - end - - context 'but the expert can’t access the linked dossier' do - it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' 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 - end - end - end - end - describe "#revoker" do - let(:avis) { create(:avis, claimant: instructeur) } - let(:procedure) { avis.procedure } + before do + patch :revoquer, params: { procedure_id: procedure.id, id: avis.id } + end it "revoke the dossier" do - patch :revoquer, params: { procedure_id: procedure.id, id: avis.id } - - expect(flash.notice).to eq("#{avis.email} ne peut plus donner son avis sur ce dossier.") + expect(flash.notice).to eq("#{avis.expert.email} ne peut plus donner son avis sur ce dossier.") end end describe 'revive' do - let(:avis) { create(:avis, claimant: instructeur, email: 'expert@gouv.fr') } - let(:procedure) { avis.procedure } - before do allow(AvisMailer).to receive(:avis_invitation).and_return(double(deliver_later: nil)) end @@ -322,141 +32,7 @@ describe Instructeurs::AvisController, type: :controller do it 'sends a reminder to the expert' do get :revive, params: { procedure_id: procedure.id, id: avis.id } expect(AvisMailer).to have_received(:avis_invitation).once.with(avis) - expect(flash.notice).to eq("Un mail de relance a été envoyé à #{avis.email}") - end - end - end - - context 'without a instructeur signed in' do - describe '#sign_up' do - let(:invited_email) { 'invited@avis.com' } - let(:dossier) { create(:dossier) } - let(:procedure) { dossier.procedure } - let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } - let(:invitations_email) { true } - - context 'when the new instructeur has never signed up' do - before do - expect(Avis).to receive(:avis_exists_and_email_belongs_to_avis?) - .with(avis.id.to_s, invited_email) - .and_return(invitations_email) - get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: invited_email } - end - - context 'when the email belongs to the invitation' do - it { expect(subject.status).to eq(200) } - it { expect(assigns(:email)).to eq(invited_email) } - it { expect(assigns(:dossier)).to eq(dossier) } - end - - context 'when the email does not belong to the invitation' do - let(:invitations_email) { false } - - it { is_expected.to redirect_to root_path } - end - end - - context 'when the instructeur has already signed up and belongs to the invitation' do - let(:instructeur) { create(:instructeur, email: invited_email) } - let!(:avis) { create(:avis, dossier: dossier, instructeur: instructeur) } - - context 'when the instructeur is authenticated' do - before do - sign_in(instructeur.user) - get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: invited_email } - end - - it { is_expected.to redirect_to instructeur_avis_url(avis.procedure, avis) } - end - - context 'when the instructeur is not authenticated' do - before do - get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: invited_email } - end - - it { is_expected.to redirect_to new_user_session_url } - end - end - - context 'when the instructeur has already signed up / is authenticated and does not belong to the invitation' do - let(:instructeur) { create(:instructeur, email: 'other@gmail.com') } - let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } - - before do - sign_in(instructeur.user) - get :sign_up, params: { id: avis.id, procedure_id: procedure.id, email: invited_email } - end - - # redirected to dossier but then the instructeur gonna be banished ! - it { is_expected.to redirect_to instructeur_avis_url(avis.procedure, avis) } - end - end - - describe '#create_instructeur' do - let(:existing_user_mail) { 'dummy@example.org' } - let!(:existing_user) { create(:user, email: existing_user_mail) } - let(:invited_email) { 'invited@avis.com' } - let(:dossier) { create(:dossier) } - let(:procedure) { dossier.procedure } - let!(:avis) { create(:avis, email: invited_email, dossier: dossier) } - let(:avis_id) { avis.id } - let(:password) { 'my-s3cure-p4ssword' } - let(:created_instructeur) { Instructeur.by_email(invited_email) } - let(:invitations_email) { true } - - before do - allow(Avis).to receive(:link_avis_to_instructeur) - expect(Avis).to receive(:avis_exists_and_email_belongs_to_avis?) - .with(avis_id.to_s, invited_email) - .and_return(invitations_email) - - post :create_instructeur, params: { - id: avis_id, - procedure_id: procedure.id, - email: invited_email, - user: { - password: password - } - } - end - - context 'when the email does not belong to the invitation' do - let(:invitations_email) { false } - - it { is_expected.to redirect_to root_path } - end - - context 'when the email belongs to the invitation' do - context 'when the instructeur creation succeeds' do - it { expect(created_instructeur).to be_present } - it { expect(created_instructeur.user.valid_password?(password)).to be true } - - it { expect(Avis).to have_received(:link_avis_to_instructeur) } - - it { expect(subject.current_instructeur).to eq(created_instructeur) } - it { is_expected.to redirect_to instructeur_all_avis_path } - - it 'creates a corresponding user account for the email' do - user = User.find_by(email: invited_email) - expect(user).to be_present - end - - context 'when there already is a user account with the same email' do - let(:existing_user_mail) { invited_email } - - it 'still creates a instructeur account' do - expect(created_instructeur).to be_present - end - end - end - - context 'when the instructeur creation fails' do - let(:password) { '' } - - it { expect(created_instructeur).to be_nil } - it { is_expected.to redirect_to sign_up_instructeur_avis_path(procedure.id, avis_id, invited_email) } - it { expect(flash.alert).to eq(['Le mot de passe doit être rempli']) } - end + expect(flash.notice).to eq("Un mail de relance a été envoyé à #{avis.expert.email}") end end end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 21985265d..fdd5beb3f 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -434,6 +434,8 @@ describe Instructeurs::DossiersController, type: :controller do end describe "#create_avis" do + let(:expert) { create(:expert) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: dossier.procedure) } let(:invite_linked_dossiers) { false } let(:saved_avis) { dossier.avis.first } let!(:old_avis_count) { Avis.count } @@ -442,7 +444,7 @@ describe Instructeurs::DossiersController, type: :controller do post :create_avis, params: { procedure_id: procedure.id, dossier_id: dossier.id, - avis: { emails: emails, introduction: 'intro', confidentiel: true, invite_linked_dossiers: invite_linked_dossiers } + avis: { emails: emails, introduction: 'intro', confidentiel: true, invite_linked_dossiers: invite_linked_dossiers, claimant: instructeur, experts_procedure: experts_procedure } } end @@ -466,7 +468,7 @@ describe Instructeurs::DossiersController, type: :controller do subject end - it { expect(saved_avis.email).to eq('email@a.com') } + it { expect(saved_avis.expert.email).to eq('email@a.com') } it { expect(saved_avis.introduction).to eq('intro') } it { expect(saved_avis.confidentiel).to eq(true) } it { expect(saved_avis.dossier).to eq(dossier) } @@ -493,7 +495,7 @@ describe Instructeurs::DossiersController, type: :controller do it { expect(flash.alert).to eq(["toto.fr : Email n'est pas valide"]) } it { expect(flash.notice).to eq("Une demande d'avis a été envoyée à titi@titimail.com") } it { expect(Avis.count).to eq(old_avis_count + 1) } - it { expect(saved_avis.email).to eq("titi@titimail.com") } + it { expect(saved_avis.expert.email).to eq("titi@titimail.com") } end context 'with linked dossiers' do @@ -507,7 +509,7 @@ describe Instructeurs::DossiersController, type: :controller do it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' do expect(flash.notice).to eq("Une demande d'avis a été envoyée à email@a.com") expect(Avis.count).to eq(old_avis_count + 1) - expect(saved_avis.email).to eq("email@a.com") + expect(saved_avis.expert.email).to eq("email@a.com") expect(saved_avis.dossier).to eq(dossier) end end @@ -526,7 +528,7 @@ describe Instructeurs::DossiersController, type: :controller do it 'sends one avis for the main dossier' do expect(flash.notice).to eq("Une demande d'avis a été envoyée à email@a.com") - expect(saved_avis.email).to eq("email@a.com") + expect(saved_avis.expert.email).to eq("email@a.com") expect(saved_avis.dossier).to eq(dossier) end @@ -540,7 +542,7 @@ describe Instructeurs::DossiersController, type: :controller do it 'sends a single avis for the main dossier, but doesn’t give access to the linked dossiers' do expect(flash.notice).to eq("Une demande d'avis a été envoyée à email@a.com") expect(Avis.count).to eq(old_avis_count + 1) - expect(saved_avis.email).to eq("email@a.com") + expect(saved_avis.expert.email).to eq("email@a.com") expect(saved_avis.dossier).to eq(dossier) end end @@ -552,7 +554,9 @@ describe Instructeurs::DossiersController, type: :controller do describe "#show" do context "when the dossier is exported as PDF" do let(:instructeur) { create(:instructeur) } + let(:expert) { create(:expert) } let(:procedure) { create(:procedure, :published, instructeurs: instructeurs) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } let(:dossier) do create(:dossier, :accepte, @@ -562,7 +566,7 @@ describe Instructeurs::DossiersController, type: :controller do :with_entreprise, :with_commentaires, procedure: procedure) end - let(:avis) { create(:avis, dossier: dossier, instructeur: instructeur) } + let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } subject do avis diff --git a/spec/controllers/instructeurs/procedures_controller_spec.rb b/spec/controllers/instructeurs/procedures_controller_spec.rb index eddbdeef4..c6993986b 100644 --- a/spec/controllers/instructeurs/procedures_controller_spec.rb +++ b/spec/controllers/instructeurs/procedures_controller_spec.rb @@ -40,47 +40,6 @@ describe Instructeurs::ProceduresController, type: :controller do end end - describe "before_action: redirect_to_avis_if_needed" do - it "is present" do - before_actions = Instructeurs::ProceduresController - ._process_action_callbacks - .filter { |process_action_callbacks| process_action_callbacks.kind == :before } - .map(&:filter) - - expect(before_actions).to include(:redirect_to_avis_if_needed) - end - end - - describe "redirect_to_avis_if_needed" do - let(:instructeur) { create(:instructeur) } - - before do - expect(@controller).to receive(:current_instructeur).at_least(:once).and_return(instructeur) - allow(@controller).to receive(:redirect_to) - end - - context "when a instructeur has some procedures" do - let!(:some_procedure) { create(:procedure, instructeurs: [instructeur]) } - - before { @controller.send(:redirect_to_avis_if_needed) } - - it "does not redirects nor flash" do - expect(@controller).not_to have_received(:redirect_to) - end - end - - context "when a instructeur has no procedure and some avis" do - before do - Avis.create!(dossier: create(:dossier), claimant: create(:instructeur), instructeur: instructeur) - @controller.send(:redirect_to_avis_if_needed) - end - - it "redirects avis" do - expect(@controller).to have_received(:redirect_to).with(instructeur_all_avis_path) - end - end - end - describe "#index" do let(:instructeur) { create(:instructeur) } subject { get :index } diff --git a/spec/controllers/invites_controller_spec.rb b/spec/controllers/invites_controller_spec.rb index ef58fc8c7..fd6416238 100644 --- a/spec/controllers/invites_controller_spec.rb +++ b/spec/controllers/invites_controller_spec.rb @@ -1,6 +1,9 @@ describe InvitesController, type: :controller do let(:dossier) { create(:dossier, :en_construction) } let(:email) { 'plop@octo.com' } + let(:expert) { create(:expert) } + let(:procedure) { create(:procedure) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } describe '#POST create' do let(:invite) { Invite.last } @@ -23,7 +26,7 @@ describe InvitesController, type: :controller do end context 'when instructeur is invited for avis on dossier' do - before { Avis.create(instructeur: signed_in_profile.instructeur, claimant: create(:instructeur), dossier: dossier) } + before { Avis.create(experts_procedure: experts_procedure, claimant: create(:instructeur), dossier: dossier) } it_behaves_like "he can not create invitation" end diff --git a/spec/features/experts/expert_spec.rb b/spec/features/experts/expert_spec.rb new file mode 100644 index 000000000..b128099fd --- /dev/null +++ b/spec/features/experts/expert_spec.rb @@ -0,0 +1,77 @@ +feature 'Inviting an expert:' do + include ActiveJob::TestHelper + include ActionView::Helpers + + context 'as an invited Expert' do + let(:expert) { create(:expert) } + let(:instructeur) { create(:instructeur) } + let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } + let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, confidentiel: true) } + + context 'when I don’t already have an account' do + scenario 'I can sign up' do + visit sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email) + + expect(page).to have_field('Email', with: avis.expert.email, disabled: true) + fill_in 'Mot de passe', with: 'This is a very complicated password !' + click_on 'Créer un compte' + + expect(page).to have_current_path(expert_all_avis_path) + expect(page).to have_text('1 avis à donner') + end + end + + context 'when I already have an existing account' do + before do + avis.expert.user.update!(last_sign_in_at: Time.zone.now) + avis.expert.user.reload + end + scenario 'I can sign in' do + visit sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email) + + expect(page).to have_current_path(new_user_session_path) + login_as avis.expert.user, scope: :user + sign_in_with(avis.expert.email, 'This is a very complicated password !') + click_on 'Passer en expert' + expect(page).to have_current_path(expert_all_avis_path) + expect(page).to have_text('1 avis à donner') + end + end + + scenario 'I can give an answer' do + avis # create avis + login_as expert.user, scope: :user + + visit expert_all_avis_path + expect(page).to have_text('1 avis à donner') + expect(page).to have_text('0 avis donnés') + + click_on '1 avis à donner' + click_on avis.dossier.user.email + within('.tabs') { click_on 'Avis' } + expect(page).to have_text("Demandeur : #{avis.claimant.email}") + expect(page).to have_text('Cet avis est confidentiel') + + fill_in 'avis_answer', with: 'Ma réponse d’expert : c’est un oui.' + find('.attachment input[name="avis[piece_justificative_file]"]').attach_file(Rails.root + 'spec/fixtures/files/RIB.pdf') + click_on 'Envoyer votre avis' + + expect(page).to have_content('Votre réponse est enregistrée') + expect(page).to have_content('Ma réponse d’expert : c’est un oui.') + expect(page).to have_content('RIB.pdf') + + within('.breadcrumbs') { click_on 'Avis' } + expect(page).to have_text('0 avis à donner') + expect(page).to have_text('1 avis donné') + end + + # TODO + # scenario 'I can read other experts advices' do + # end + + # scenario 'I can invite other experts' do + # end + end +end diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index 442020333..2237560f2 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -3,7 +3,7 @@ feature 'Inviting an expert:' do include ActionView::Helpers let(:instructeur) { create(:instructeur, password: 'my-s3cure-p4ssword') } - let(:expert) { create(:instructeur, password: expert_password) } + let(:expert) { create(:expert, password: expert_password) } let(:expert_password) { 'mot de passe d’expert' } let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } @@ -20,7 +20,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: 'expert1@exemple.fr, expert2@exemple.fr' + fill_in 'avis_emails', with: 'expert1@expert.com, expert2@expert.com' 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' @@ -31,23 +31,24 @@ feature 'Inviting an expert:' do expect(page).to have_content('Une demande d\'avis a été envoyée') expect(page).to have_content('Avis des invités') within('.list-avis') do - expect(page).to have_content('expert1@exemple.fr') - expect(page).to have_content('expert2@exemple.fr') + expect(page).to have_content('expert1@expert.com') + expect(page).to have_content('expert2@expert.com') expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.') end expect(Avis.count).to eq(4) - expect(emails_sent_to('expert1@exemple.fr').size).to eq(1) - expect(emails_sent_to('expert2@exemple.fr').size).to eq(1) + expect(emails_sent_to('expert1@expert.com').size).to eq(1) + expect(emails_sent_to('expert2@expert.com').size).to eq(1) - invitation_email = open_email('expert2@exemple.fr') - avis = Avis.find_by(email: 'expert2@exemple.fr', dossier: dossier) - sign_up_link = sign_up_instructeur_avis_path(avis.dossier.procedure, avis, avis.email) + invitation_email = open_email('expert1@expert.com') + avis = Avis.find_by(expert: expert.id) + sign_up_link = sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email) expect(invitation_email.body).to include(sign_up_link) end context 'when experts submitted their answer' do - let!(:answered_avis) { create(:avis, :with_answer, dossier: dossier, claimant: instructeur, email: expert.email) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + let!(:answered_avis) { create(:avis, :with_answer, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } scenario 'I can read the expert answer' do login_as instructeur.user, scope: :user @@ -55,80 +56,11 @@ feature 'Inviting an expert:' do click_on 'Avis externes' - expect(page).to have_content(expert.email) + expect(page).to have_content(answered_avis.expert.email) answered_avis.answer.split("\n").each do |answer_line| expect(page).to have_content(answer_line) end end end end - - context 'as an invited Expert' do - let(:avis_email) { expert.email } - let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, email: avis_email, confidentiel: true) } - - context 'when I don’t already have an account' do - let(:avis_email) { 'not-signed-up-expert@exemple.fr' } - - scenario 'I can sign up' do - visit sign_up_instructeur_avis_path(avis.dossier.procedure, avis, avis_email) - - expect(page).to have_field('Email', with: avis_email, disabled: true) - fill_in 'Mot de passe', with: 'This is a very complicated password !' - click_on 'Créer un compte' - - expect(page).to have_current_path(instructeur_all_avis_path) - expect(page).to have_text('1 avis à donner') - end - end - - context 'when I already have an existing account' do - let(:avis_email) { expert.email } - - scenario 'I can sign in' do - visit sign_up_instructeur_avis_path(avis.dossier.procedure, avis, avis_email) - - expect(page).to have_current_path(new_user_session_path) - sign_in_with(expert.email, expert_password) - - expect(page).to have_current_path(instructeur_all_avis_path) - expect(page).to have_text('1 avis à donner') - end - end - - scenario 'I can give an answer' do - avis # create avis - login_as expert.user, scope: :user - - visit instructeur_all_avis_path - expect(page).to have_text('1 avis à donner') - expect(page).to have_text('0 avis donnés') - - click_on '1 avis à donner' - click_on avis.dossier.user.email - - within('.tabs') { click_on 'Avis' } - expect(page).to have_text("Demandeur : #{instructeur.email}") - expect(page).to have_text('Cet avis est confidentiel') - - fill_in 'avis_answer', with: 'Ma réponse d’expert : c’est un oui.' - find('.attachment input[name="avis[piece_justificative_file]"]').attach_file(Rails.root + 'spec/fixtures/files/RIB.pdf') - click_on 'Envoyer votre avis' - - expect(page).to have_content('Votre réponse est enregistrée') - expect(page).to have_content('Ma réponse d’expert : c’est un oui.') - expect(page).to have_content('RIB.pdf') - - within('.new-header') { click_on 'Avis' } - expect(page).to have_text('0 avis à donner') - expect(page).to have_text('1 avis donné') - end - - # TODO - # scenario 'I can read other experts advices' do - # end - - # scenario 'I can invite other experts' do - # end - end end diff --git a/spec/helpers/dossier_link_helper_spec.rb b/spec/helpers/dossier_link_helper_spec.rb index c8f04fab1..286ed4cda 100644 --- a/spec/helpers/dossier_link_helper_spec.rb +++ b/spec/helpers/dossier_link_helper_spec.rb @@ -27,9 +27,10 @@ describe DossierLinkHelper do context "when access as expert" do let(:dossier) { create(:dossier) } let(:instructeur) { create(:instructeur) } - let!(:avis) { create(:avis, dossier: dossier, instructeur: instructeur) } - - it { expect(helper.dossier_linked_path(instructeur, dossier)).to eq(instructeur_avis_path(avis.dossier.procedure, avis)) } + let(:expert) { create(:expert) } + let!(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: dossier.procedure) } + let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } + it { expect(helper.dossier_linked_path(expert, dossier)).to eq(expert_avis_path(avis.dossier.procedure, avis)) } end context "when access as user" do diff --git a/spec/mailers/avis_mailer_spec.rb b/spec/mailers/avis_mailer_spec.rb index 163e77205..6ea8aba97 100644 --- a/spec/mailers/avis_mailer_spec.rb +++ b/spec/mailers/avis_mailer_spec.rb @@ -1,6 +1,10 @@ RSpec.describe AvisMailer, type: :mailer do describe '.avis_invitation' do - let(:avis) { create(:avis) } + let(:claimant) { create(:instructeur) } + let(:expert) { create(:expert) } + let(:dossier) { create(:dossier) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: dossier.procedure) } + let(:avis) { Avis.create(dossier: dossier, claimant: claimant, experts_procedure: experts_procedure, introduction: 'intro') } subject { described_class.avis_invitation(avis) } @@ -10,12 +14,7 @@ RSpec.describe AvisMailer, type: :mailer do it { expect(subject.body).to include(instructeur_avis_url(avis.dossier.procedure.id, avis)) } context 'when the recipient is not already registered' do - before do - avis.email = 'instructeur@email.com' - avis.instructeur = nil - end - - it { expect(subject.body).to include(sign_up_instructeur_avis_url(avis.dossier.procedure.id, avis.id, avis.email)) } + it { expect(subject.body).to include(sign_up_expert_avis_url(avis.dossier.procedure.id, avis.id, avis.expert.email)) } end end end diff --git a/spec/models/avis_spec.rb b/spec/models/avis_spec.rb index 90f10ce76..e0803f0f4 100644 --- a/spec/models/avis_spec.rb +++ b/spec/models/avis_spec.rb @@ -3,22 +3,16 @@ RSpec.describe Avis, type: :model do describe '#email_to_display' do let(:invited_email) { 'invited@avis.com' } - let!(:avis) do - avis = create(:avis, email: invited_email, dossier: create(:dossier)) - avis.instructeur = nil - avis - end + let(:expert) { create(:expert) } + let(:procedure) { create(:procedure) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } subject { avis.email_to_display } - context 'when instructeur is not known' do - it { is_expected.to eq(invited_email) } - end + context 'when expert is known' do + let!(:avis) { create(:avis, claimant: claimant, dossier: create(:dossier), experts_procedure: experts_procedure) } - context 'when instructeur is known' do - let!(:avis) { create(:avis, email: nil, instructeur: create(:instructeur), dossier: create(:dossier)) } - - it { is_expected.to eq(avis.instructeur.email) } + it { is_expected.to eq(avis.expert.email) } end end @@ -34,28 +28,6 @@ RSpec.describe Avis, type: :model do end end - describe ".link_avis_to_instructeur" do - let(:instructeur) { create(:instructeur) } - - subject { Avis.link_avis_to_instructeur(instructeur) } - - context 'when there are 2 avis linked by email to a instructeur' do - let!(:avis) { create(:avis, email: instructeur.email, instructeur: nil) } - let!(:avis2) { create(:avis, email: instructeur.email, instructeur: nil) } - - before do - subject - avis.reload - avis2.reload - end - - it { expect(avis.email).to be_nil } - it { expect(avis.instructeur).to eq(instructeur) } - it { expect(avis2.email).to be_nil } - it { expect(avis2.instructeur).to eq(instructeur) } - end - end - describe "an avis is linked to an expert_procedure" do let(:procedure) { create(:procedure) } let(:expert) { create(:expert) } @@ -102,31 +74,14 @@ RSpec.describe Avis, type: :model do end end - describe '#try_to_assign_instructeur' do - let!(:instructeur) { create(:instructeur) } - let(:avis) { create(:avis, claimant: claimant, email: email, dossier: create(:dossier)) } - - context 'when the email belongs to a instructeur' do - let(:email) { instructeur.email } - - it { expect(avis.instructeur).to eq(instructeur) } - it { expect(avis.email).to be_nil } - end - - context 'when the email does not belongs to a instructeur' do - let(:email) { 'unknown@email' } - - it { expect(avis.instructeur).to be_nil } - it { expect(avis.email).to eq(email) } - end - end - describe "email sanitization" do - subject { Avis.create(claimant: claimant, email: email, dossier: create(:dossier), instructeur: create(:instructeur)) } + let(:expert) { create(:expert) } + let(:procedure) { create(:procedure) } + let!(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + subject { Avis.create(claimant: claimant, email: email, experts_procedure: experts_procedure, dossier: create(:dossier)) } context "when there is no email" do let(:email) { nil } - it { expect(subject.email).to be_nil } end @@ -191,12 +146,13 @@ RSpec.describe Avis, type: :model do let(:procedure) { create(:procedure, :published, instructeurs: instructeurs) } let(:dossier) { create(:dossier, :en_instruction, procedure: procedure) } let(:claimant_expert) { create(:instructeur) } - let(:expert) { create(:instructeur) } - let(:another_expert) { create(:instructeur) } + let(:expert) { create(:expert) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + let(:another_expert) { create(:expert) } context "when avis claimed by an expert" do - let(:avis) { create(:avis, dossier: dossier, claimant: claimant_expert, instructeur: expert) } - let(:another_avis) { create(:avis, dossier: dossier, claimant: instructeur, instructeur: another_expert) } + let(:avis) { create(:avis, dossier: dossier, claimant: claimant_expert, experts_procedure: experts_procedure) } + let(:another_avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } it "is revokable by this expert or any instructeurs of the dossier" do expect(avis.revokable_by?(claimant_expert)).to be_truthy expect(avis.revokable_by?(another_expert)).to be_falsy @@ -205,8 +161,13 @@ RSpec.describe Avis, type: :model do end context "when avis claimed by an instructeur" do - let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, instructeur: expert) } - let(:another_avis) { create(:avis, dossier: dossier, claimant: expert, instructeur: another_expert) } + let(:expert) { create(:expert) } + let(:expert_2) { create(:expert) } + let!(:procedure) { create(:procedure, :published, instructeurs: instructeurs) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } + let(:experts_procedure_2) { ExpertsProcedure.create(expert: expert_2, procedure: procedure) } + let(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } + let(:another_avis) { create(:avis, dossier: dossier, claimant: expert, experts_procedure: experts_procedure_2) } let(:another_instructeur) { create(:instructeur) } let(:instructeurs) { [instructeur, another_instructeur] } diff --git a/spec/models/dossier_spec.rb b/spec/models/dossier_spec.rb index e12ff5879..fabbe9366 100644 --- a/spec/models/dossier_spec.rb +++ b/spec/models/dossier_spec.rb @@ -307,49 +307,50 @@ describe Dossier do let!(:instructeur) { create(:instructeur) } let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } let!(:dossier) { create(:dossier, procedure: procedure, state: Dossier.states.fetch(:en_construction)) } - - let!(:expert_1) { create(:instructeur) } - let!(:expert_2) { create(:instructeur) } + let!(:experts_procedure) { ExpertsProcedure.create(expert: expert_1, procedure: procedure) } + let!(:experts_procedure_2) { ExpertsProcedure.create(expert: expert_2, procedure: procedure) } + let!(:expert_1) { create(:expert) } + let!(:expert_2) { create(:expert) } context 'when there is a public advice asked from the dossiers instructeur' do - let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, instructeur: expert_1, confidentiel: false) } + let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, confidentiel: false) } - it { expect(dossier.avis_for(instructeur)).to match([avis]) } - it { expect(dossier.avis_for(expert_1)).to match([avis]) } - it { expect(dossier.avis_for(expert_2)).to match([avis]) } + it { expect(dossier.avis_for_instructeur(instructeur)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_1)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_2)).to match([avis]) } end context 'when there is a private advice asked from the dossiers instructeur' do - let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, instructeur: expert_1, confidentiel: true) } + let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure, confidentiel: true) } - it { expect(dossier.avis_for(instructeur)).to match([avis]) } - it { expect(dossier.avis_for(expert_1)).to match([avis]) } - it { expect(dossier.avis_for(expert_2)).to match([]) } + it { expect(dossier.avis_for_instructeur(instructeur)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_1)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_2)).not_to match([avis]) } end context 'when there is a public advice asked from one expert to another' do - let!(:avis) { Avis.create(dossier: dossier, claimant: expert_1, instructeur: expert_2, confidentiel: false) } + let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure_2, confidentiel: false) } - it { expect(dossier.avis_for(instructeur)).to match([avis]) } - it { expect(dossier.avis_for(expert_1)).to match([avis]) } - it { expect(dossier.avis_for(expert_2)).to match([avis]) } + it { expect(dossier.avis_for_instructeur(instructeur)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_1)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_2)).to match([avis]) } end context 'when there is a private advice asked from one expert to another' do - let!(:avis) { Avis.create(dossier: dossier, claimant: expert_1, instructeur: expert_2, confidentiel: true) } + let!(:avis) { Avis.create(dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure_2, confidentiel: true) } - it { expect(dossier.avis_for(instructeur)).to match([avis]) } - it { expect(dossier.avis_for(expert_1)).to match([avis]) } - it { expect(dossier.avis_for(expert_2)).to match([avis]) } + it { expect(dossier.avis_for_instructeur(instructeur)).to match([avis]) } + it { expect(dossier.avis_for_expert(expert_1)).not_to match([avis]) } + it { expect(dossier.avis_for_expert(expert_2)).to match([avis]) } end context 'when they are a lot of advice' do - let!(:avis_1) { Avis.create(dossier: dossier, claimant: expert_1, instructeur: expert_2, confidentiel: false, created_at: Time.zone.parse('10/01/2010')) } - let!(:avis_2) { Avis.create(dossier: dossier, claimant: expert_1, instructeur: expert_2, confidentiel: false, created_at: Time.zone.parse('9/01/2010')) } - let!(:avis_3) { Avis.create(dossier: dossier, claimant: expert_1, instructeur: expert_2, confidentiel: false, created_at: Time.zone.parse('11/01/2010')) } + let!(:avis_1) { Avis.create(dossier: dossier, claimant: expert_1, experts_procedure: experts_procedure_2, confidentiel: false, created_at: Time.zone.parse('10/01/2010'), tmp_expert_migrated: true) } + let!(:avis_2) { Avis.create(dossier: dossier, claimant: expert_1, experts_procedure: experts_procedure_2, confidentiel: false, created_at: Time.zone.parse('9/01/2010'), tmp_expert_migrated: true) } + let!(:avis_3) { Avis.create(dossier: dossier, claimant: expert_1, experts_procedure: experts_procedure_2, confidentiel: false, created_at: Time.zone.parse('11/01/2010'), tmp_expert_migrated: true) } - it { expect(dossier.avis_for(instructeur)).to match([avis_2, avis_1, avis_3]) } - it { expect(dossier.avis_for(expert_1)).to match([avis_2, avis_1, avis_3]) } + it { expect(dossier.avis_for_instructeur(instructeur)).to match([avis_2, avis_1, avis_3]) } + it { expect(dossier.avis_for_expert(expert_1)).to match([avis_2, avis_1, avis_3]) } end end diff --git a/spec/models/experts_procedure_spec.rb b/spec/models/experts_procedure_spec.rb index b6dd1a7b8..4b4f6791f 100644 --- a/spec/models/experts_procedure_spec.rb +++ b/spec/models/experts_procedure_spec.rb @@ -1,6 +1,7 @@ RSpec.describe ExpertsProcedure, type: :model do describe '#invited_expert_emails' do let!(:procedure) { create(:procedure, :published) } + let(:claimant) { create(:instructeur) } let(:expert) { create(:expert) } let(:expert2) { create(:expert) } let(:expert3) { create(:expert) } @@ -13,7 +14,7 @@ RSpec.describe ExpertsProcedure, type: :model do let!(:dossier) { create(:dossier, procedure: procedure) } context 'when a procedure has one avis and known instructeur' do - let!(:avis) { create(:avis, dossier: dossier, instructeur: create(:instructeur, email: expert.email), experts_procedure: experts_procedure) } + let!(:avis) { create(:avis, dossier: dossier, claimant: claimant, experts_procedure: experts_procedure) } it { is_expected.to eq([experts_procedure]) } it { expect(procedure.experts.count).to eq(1) } diff --git a/spec/views/instructeur/avis/instruction.html.haml_spec.rb b/spec/views/experts/avis/instruction.html.haml.spec.rb similarity index 63% rename from spec/views/instructeur/avis/instruction.html.haml_spec.rb rename to spec/views/experts/avis/instruction.html.haml.spec.rb index ef112aba3..fbc72358d 100644 --- a/spec/views/instructeur/avis/instruction.html.haml_spec.rb +++ b/spec/views/experts/avis/instruction.html.haml.spec.rb @@ -1,12 +1,15 @@ -describe 'instructeurs/avis/instruction.html.haml', type: :view do - let(:expert) { create(:instructeur) } - let(:avis) { create(:avis, confidentiel: confidentiel, email: expert.email) } +describe 'experts/avis/instruction.html.haml', type: :view do + let(:expert) { create(:expert) } + let(:claimant) { create(:instructeur) } + let(:procedure) { create(:procedure) } + let!(:avis) { create(:avis, confidentiel: confidentiel, claimant: claimant, experts_procedure: experts_procedure) } + let!(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure) } before do assign(:avis, avis) assign(:new_avis, Avis.new) assign(:dossier, avis.dossier) - allow(view).to receive(:current_instructeur).and_return(avis.instructeur) + allow(view).to receive(:current_expert).and_return(avis.expert) end subject { render } diff --git a/spec/views/instructeur/shared/avis/list.html.haml_spec.rb b/spec/views/instructeur/shared/avis/list.html.haml_spec.rb index 507cbf222..1b4b5fe17 100644 --- a/spec/views/instructeur/shared/avis/list.html.haml_spec.rb +++ b/spec/views/instructeur/shared/avis/list.html.haml_spec.rb @@ -4,7 +4,10 @@ describe 'instructeurs/shared/avis/_list.html.haml', type: :view do subject { render 'instructeurs/shared/avis/list.html.haml', avis: avis, avis_seen_at: seen_at, current_instructeur: instructeur } let(:instructeur) { create(:instructeur) } - let(:avis) { [create(:avis, claimant: instructeur)] } + let(:expert) { create(:expert) } + let!(:dossier) { create(:dossier) } + let(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: dossier.procedure) } + let(:avis) { [create(:avis, claimant: instructeur, experts_procedure: experts_procedure)] } let(:seen_at) { avis.first.created_at + 1.hour } it { is_expected.to have_text(avis.first.introduction) } @@ -17,7 +20,7 @@ describe 'instructeurs/shared/avis/_list.html.haml', type: :view do end context 'with an answer' do - let(:avis) { [create(:avis, :with_answer, claimant: instructeur)] } + let(:avis) { [create(:avis, :with_answer, claimant: instructeur, experts_procedure: experts_procedure)] } it 'renders the answer formatted with newlines' do expect(subject).to include(simple_format(avis.first.answer)) diff --git a/spec/views/shared/dossiers/_champs.html.haml_spec.rb b/spec/views/shared/dossiers/_champs.html.haml_spec.rb index a441f7ef5..f86192a15 100644 --- a/spec/views/shared/dossiers/_champs.html.haml_spec.rb +++ b/spec/views/shared/dossiers/_champs.html.haml_spec.rb @@ -12,7 +12,6 @@ describe 'shared/dossiers/champs.html.haml', type: :view do context "there are some champs" do let(:dossier) { create(:dossier) } - let(:avis) { create :avis, dossier: dossier, instructeur: instructeur } let(:champ1) { create(:champ_checkbox, dossier: dossier, value: "on") } let(:champ2) { create(:champ_header_section, dossier: dossier, value: "Section") } let(:champ3) { create(:champ_explication, dossier: dossier, value: "mazette") } @@ -20,8 +19,6 @@ describe 'shared/dossiers/champs.html.haml', type: :view do let(:champ5) { create(:champ_textarea, dossier: dossier, value: "Some long text in a textarea.") } let(:champs) { [champ1, champ2, champ3, champ4, champ5] } - before { dossier.avis << avis } - it "renders titles and values of champs" do expect(subject).to include(champ1.libelle) expect(subject).to include(champ1.value) @@ -29,7 +26,6 @@ describe 'shared/dossiers/champs.html.haml', type: :view do expect(subject).to have_css(".header-section") expect(subject).to include(champ2.libelle) - expect(subject).to have_link("Dossier nº #{dossier.id}") expect(subject).to include(dossier.text_summary) expect(subject).to include(champ5.libelle) From 5e0cfbea07982a238d0c514a605bdef7dd96f0c3 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 11 Mar 2021 17:03:45 +0100 Subject: [PATCH 16/23] add tmp_expert_migrated to avis table --- db/migrate/20210311141956_add_tmp_expert_migrated_to_avis.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20210311141956_add_tmp_expert_migrated_to_avis.rb diff --git a/db/migrate/20210311141956_add_tmp_expert_migrated_to_avis.rb b/db/migrate/20210311141956_add_tmp_expert_migrated_to_avis.rb new file mode 100644 index 000000000..2f1341fca --- /dev/null +++ b/db/migrate/20210311141956_add_tmp_expert_migrated_to_avis.rb @@ -0,0 +1,5 @@ +class AddTmpExpertMigratedToAvis < ActiveRecord::Migration[6.0] + def change + add_column :avis, :tmp_expert_migrated, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index f2bafeffb..be8f20e8a 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_07_143807) do +ActiveRecord::Schema.define(version: 2021_03_11_141956) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -123,6 +123,7 @@ ActiveRecord::Schema.define(version: 2021_03_07_143807) do t.datetime "revoked_at" t.bigint "experts_procedure_id" t.string "claimant_type" + t.boolean "tmp_expert_migrated", default: false t.index ["claimant_id"], name: "index_avis_on_claimant_id" t.index ["dossier_id"], name: "index_avis_on_dossier_id" t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id" From 5e88ecc24c102f68247471b64fa1143cd942bdf1 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Tue, 16 Mar 2021 16:57:58 +0100 Subject: [PATCH 17/23] improve request to find an expert --- app/models/avis.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/avis.rb b/app/models/avis.rb index 2c6b68a99..b2e67b1bc 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -63,7 +63,7 @@ class Avis < ApplicationRecord if claimant_type == 'Instructeur' || !tmp_expert_migrated Instructeur.find(claimant_id) else - Expert.find(claimant_id).user.expert + Expert.find(claimant_id) end end From 64b94100f4c87af37e79dcbd1251a041aa72abb0 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 18 Mar 2021 12:49:50 +0100 Subject: [PATCH 18/23] fix dossier link helper --- app/helpers/dossier_link_helper.rb | 5 ----- spec/helpers/dossier_link_helper_spec.rb | 9 --------- 2 files changed, 14 deletions(-) diff --git a/app/helpers/dossier_link_helper.rb b/app/helpers/dossier_link_helper.rb index 4dacd9960..b5764a3f5 100644 --- a/app/helpers/dossier_link_helper.rb +++ b/app/helpers/dossier_link_helper.rb @@ -4,11 +4,6 @@ module DossierLinkHelper if user.groupe_instructeurs.include?(dossier.groupe_instructeur) instructeur_dossier_path(dossier.procedure, dossier) end - elsif user.is_a?(Expert) - avis = dossier.avis.find_by(expert: user.user) - if avis.present? - expert_avis_path(avis.procedure, avis) - end elsif user.owns_or_invite?(dossier) dossier_path(dossier) end diff --git a/spec/helpers/dossier_link_helper_spec.rb b/spec/helpers/dossier_link_helper_spec.rb index 286ed4cda..d2b4020e4 100644 --- a/spec/helpers/dossier_link_helper_spec.rb +++ b/spec/helpers/dossier_link_helper_spec.rb @@ -24,15 +24,6 @@ describe DossierLinkHelper do it { expect(helper.dossier_linked_path(instructeur, dossier)).to eq(instructeur_dossier_path(dossier.procedure, dossier)) } end - context "when access as expert" do - let(:dossier) { create(:dossier) } - let(:instructeur) { create(:instructeur) } - let(:expert) { create(:expert) } - let!(:experts_procedure) { ExpertsProcedure.create(expert: expert, procedure: dossier.procedure) } - let!(:avis) { create(:avis, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) } - it { expect(helper.dossier_linked_path(expert, dossier)).to eq(expert_avis_path(avis.dossier.procedure, avis)) } - end - context "when access as user" do let(:dossier) { create(:dossier) } let(:user) { create(:user) } From bb47c88c5fa66ebcbb576065f71a8acc2b6fda95 Mon Sep 17 00:00:00 2001 From: kara Diaby Date: Thu, 18 Mar 2021 13:53:05 +0100 Subject: [PATCH 19/23] fix expert spec feature --- spec/features/instructeurs/expert_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index 2237560f2..1005af334 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -4,6 +4,7 @@ feature 'Inviting an expert:' do let(:instructeur) { create(:instructeur, password: 'my-s3cure-p4ssword') } let(:expert) { create(:expert, password: expert_password) } + let(:expert2) { create(:expert, password: expert_password) } let(:expert_password) { 'mot de passe d’expert' } let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } @@ -20,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: 'expert1@expert.com, expert2@expert.com' + fill_in 'avis_emails', with: "#{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' @@ -31,16 +32,16 @@ feature 'Inviting an expert:' do expect(page).to have_content('Une demande d\'avis a été envoyée') expect(page).to have_content('Avis des invités') within('.list-avis') do - expect(page).to have_content('expert1@expert.com') - expect(page).to have_content('expert2@expert.com') + expect(page).to have_content(expert.email.to_s) + expect(page).to have_content(expert2.email.to_s) expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.') end expect(Avis.count).to eq(4) - expect(emails_sent_to('expert1@expert.com').size).to eq(1) - expect(emails_sent_to('expert2@expert.com').size).to eq(1) + expect(emails_sent_to(expert.email.to_s).size).to eq(1) + expect(emails_sent_to(expert2.email.to_s).size).to eq(1) - invitation_email = open_email('expert1@expert.com') + invitation_email = open_email(expert.email.to_s) avis = Avis.find_by(expert: expert.id) sign_up_link = sign_up_expert_avis_path(avis.dossier.procedure, avis, avis.expert.email) expect(invitation_email.body).to include(sign_up_link) From 1449fbbe67e8078e81cdcba5bb152224da8b0ee6 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 18 Mar 2021 14:44:14 +0100 Subject: [PATCH 20/23] Fix multiple_drop_down_list mandatory check --- app/models/champ.rb | 2 ++ spec/models/champ_shared_example.rb | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/app/models/champ.rb b/app/models/champ.rb index ef9072e49..864ced9fb 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -94,6 +94,8 @@ class Champ < ApplicationRecord case type_de_champ.type_champ when TypeDeChamp.type_champs.fetch(:carte) geo_areas.blank? || value == '[]' + when TypeDeChamp.type_champs.fetch(:multiple_drop_down_list) + value.blank? || value == '[]' else value.blank? end diff --git a/spec/models/champ_shared_example.rb b/spec/models/champ_shared_example.rb index 8ed267e37..1e32f858b 100644 --- a/spec/models/champ_shared_example.rb +++ b/spec/models/champ_shared_example.rb @@ -15,6 +15,12 @@ shared_examples 'champ_spec' do it { expect(champ.mandatory_and_blank?).to be(true) } end + context 'when multiple_drop_down_list mandatory and blank' do + let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, mandatory: mandatory) } + let(:value) { '[]' } + it { expect(champ.mandatory_and_blank?).to be(true) } + end + context 'when not blank' do let(:value) { 'yop' } it { expect(champ.mandatory_and_blank?).to be(false) } From 239169e9252f7f36f0789d4be518b20a77476409 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 18 Mar 2021 16:23:21 +0000 Subject: [PATCH 21/23] dossier: fix looking-up avis from Expert MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This line causes an error on Rails 6.1. And it isn't even necessary: this line is supposed to query avis where the expert advice has been requested – but this is handled by the `if expert.dossiers.include?(self)` condition just above. --- app/models/dossier.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 8b4d4bbee..b14893c5e 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -497,7 +497,6 @@ class Dossier < ApplicationRecord .where(confidentiel: false) .or(avis.where(claimant_id: expert.id, claimant_type: 'Expert', tmp_expert_migrated: true)) .or(avis.where(claimant_id: instructeur, claimant_type: 'Instructeur', tmp_expert_migrated: false)) - .or(avis.where(expert: expert)) .order(created_at: :asc) end end From 1cb430a87a653dd1582538f2bcec78bd51b230d1 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Fri, 19 Mar 2021 06:48:34 +0000 Subject: [PATCH 22/23] lib: remove unused AttestationClosedMailDiscrepancyMailer --- ...estation_closed_mail_discrepancy_mailer.rb | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 lib/mailers/attestation_closed_mail_discrepancy_mailer.rb diff --git a/lib/mailers/attestation_closed_mail_discrepancy_mailer.rb b/lib/mailers/attestation_closed_mail_discrepancy_mailer.rb deleted file mode 100644 index 913db07c7..000000000 --- a/lib/mailers/attestation_closed_mail_discrepancy_mailer.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Mailers - class AttestationClosedMailDiscrepancyMailer < ApplicationMailer - include Rails.application.routes.url_helpers - - def missing_attestation_tag_email(admin, procedures) - procedures = procedures.sort_by(&:id) - mail(to: admin.email, subject: subject(procedures), body: body(procedures)) - end - - private - - def subject(procedures) - if procedures.count == 1 - procedure_ids = "votre démarche nº #{procedures.first.id}" - else - procedure_ids = 'vos démarches nº ' + procedures.map(&:id).join(', ') - end - "#{APPLICATION_NAME} – mise à jour nécessaire de l’accusé d’acceptation de #{procedure_ids}" - end - - def body(procedures) - <<~HEREDOC - Bonjour, - - Pour des raisons de confidentialité, le mode de transmission des attestations aux usagers évolue. - - À compter du 30 avril, les mails d’accusé d’acceptation émis par #{APPLICATION_NAME} ne - comporteront plus d’attestation en pièce jointe comme c’est le cas aujourd’hui. - - À la place, le mail contiendra un lien permettant à l’usager de télécharger son - attestation dirctement dans son espace sécurisé sur #{APPLICATION_NAME}. - - Ce lien de téléchargement est généré par la balise --lien attestation--. - - #{detail_procedures(procedures)} - - Pour toute question vous pouvez nous joindre par téléphone au #{CONTACT_PHONE} - ou sur l’adresse email #{CONTACT_EMAIL}. - -- \nL’équipe #{APPLICATION_NAME} - HEREDOC - end - - def detail_procedures(procedures) - if procedures.count == 1 - p = procedures.first - - <<~HEREDOC.chomp - Vous êtes administrateur de la démarche suivante : - #{p.libelle} (nº #{p.id}) - - Cette démarche donne lieu à l’émission d’une attestation, et son accusé - d’acceptation a été personnalisé. Pour respecter la rédaction de votre accusé - d’acceptation, nous ne prendrons pas l’initiative d’y ajouter la balise --lien attestation--. - - Afin que vos usagers puissent continuer à accéder facilement à leurs attestations - dans leurs démarches futures, nous vous invitons à ajouter à votre convenance la - balise --lien attestation-- dans votre accusé d’acceptation. Vous pouvez le faire en - cliquant sur le lien suivant : - - #{edit_admin_procedure_mail_template_url(p, Mails::ClosedMail::SLUG)} - HEREDOC - else - liste_procedures = procedures.map { |p| "- #{p.libelle} (nº #{p.id}) – #{edit_admin_procedure_mail_template_url(p, Mails::ClosedMail::SLUG)}" }.join("\n") - - <<~HEREDOC.chomp - Vous êtes administrateur sur plusieurs démarches qui donnent lieu à l’émission - d’une attestation, et dont l’accusé d’acceptation a été personnalisé. Pour respecter - la rédaction de vos accusés d’acceptation, nous ne prendrons pas l’initiative d’y - ajouter de balise --lien attestation--. - - Afin que vos usagers puissent continuer à accéder facilement à leurs attestations - dans leurs démarches futures, nous vous invitons à ajouter à votre convenance la - balise --lien attestation-- dans vos accusés d’acceptation. - - Vous trouverez ci-après la liste des démarches concernées, ainsi que les liens vous - permettant d’éditer les accusés d’acceptation correspondants. - - #{liste_procedures} - HEREDOC - end - end - end -end From 56b3601063c9563c9f8db95507fa90fd15cf0422 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Thu, 18 Mar 2021 17:22:59 +0100 Subject: [PATCH 23/23] app: enable Bootsnap Bootsnap speeds up the initial loading of the Rails app by: - Optimizing the LOAD_PATH dynamically - Caching the result of Ruby bytecode compilation Cached data are written to `tmp/cache/bootsnap*`. This is enabled in the default Rails app template. --- Gemfile | 1 + Gemfile.lock | 4 ++++ config/boot.rb | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 14898ae50..8be7243c8 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,7 @@ gem 'administrate' gem 'after_party' gem 'anchored' gem 'bcrypt' +gem 'bootsnap', '>= 1.4.4', require: false # Reduces boot times through caching; required in config/boot.rb gem 'browser' gem 'chartkick' gem 'chunky_png' diff --git a/Gemfile.lock b/Gemfile.lock index 74145f600..03737005a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,6 +134,8 @@ GEM bcrypt (3.1.16) bindata (2.4.8) bindex (0.8.1) + bootsnap (1.7.2) + msgpack (~> 1.0) brakeman (5.0.0) browser (5.3.0) builder (3.2.4) @@ -419,6 +421,7 @@ GEM minitest (5.14.4) momentjs-rails (2.20.1) railties (>= 3.1) + msgpack (1.4.2) multi_json (1.15.0) multipart-post (2.1.1) mustermann (1.1.1) @@ -781,6 +784,7 @@ DEPENDENCIES annotate axe-matchers bcrypt + bootsnap (>= 1.4.4) brakeman browser capybara diff --git a/config/boot.rb b/config/boot.rb index 30f5120df..3cda23b4d 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,3 +1,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -require 'bundler/setup' # Set up gems listed in the Gemfile. +require "bundler/setup" # Set up gems listed in the Gemfile. +require "bootsnap/setup" # Speed up boot time by caching expensive operations.