diff --git a/Gemfile b/Gemfile index a7b346436..988861a90 100644 --- a/Gemfile +++ b/Gemfile @@ -4,7 +4,7 @@ gem 'aasm' gem 'actiontext', git: 'https://github.com/kobaltz/actiontext.git', branch: 'archive', require: 'action_text' # Port of ActionText to Rails 5 gem 'active_link_to' # Automatically set a class on active links gem 'active_model_serializers' -gem 'activestorage-openstack', git: 'https://github.com/fredZen/activestorage-openstack.git', branch: 'frederic/fix_upload_signature' +gem 'activestorage-openstack' gem 'administrate' gem 'after_party' gem 'anchored' diff --git a/Gemfile.lock b/Gemfile.lock index 1336fb19c..41c577d6b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,3 @@ -GIT - remote: https://github.com/fredZen/activestorage-openstack.git - revision: c71d5107a51701eab9d9267dd0000e6c1cf3e39a - branch: frederic/fix_upload_signature - specs: - activestorage-openstack (0.5.0) - fog-openstack (~> 1.0) - marcel - mime-types - rails (~> 5.2.0) - GIT remote: https://github.com/kobaltz/actiontext.git revision: ef59c4ba99d1b7614dd47f5a294eef553224db88 @@ -75,13 +64,18 @@ GEM actionpack (= 5.2.2.1) activerecord (= 5.2.2.1) marcel (~> 0.3.1) + activestorage-openstack (1.0.0) + fog-openstack (~> 1.0) + marcel + mime-types + rails (<= 6) activesupport (5.2.2.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) administrate (0.11.0) actionpack (>= 4.2, < 6.0) actionview (>= 4.2, < 6.0) @@ -125,18 +119,18 @@ GEM browser (2.5.3) builder (3.2.3) byebug (10.0.2) - capybara (3.12.0) + capybara (3.29.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.2) + regexp_parser (~> 1.5) xpath (~> 3.2) capybara-email (3.0.1) capybara (>= 2.4, < 4.0) mail - capybara-screenshot (1.0.22) + capybara-screenshot (1.0.23) capybara (>= 1.0, < 4) launchy capybara-selenium (0.0.6) @@ -379,7 +373,7 @@ GEM mime-types-data (~> 3.2015) mime-types-data (3.2018.0812) mimemagic (0.3.3) - mini_mime (1.0.1) + mini_mime (1.0.2) mini_portile2 (2.4.0) minitest (5.11.3) momentjs-rails (2.20.1) @@ -454,7 +448,7 @@ GEM pry-byebug (3.6.0) byebug (~> 10.0) pry (~> 0.10) - public_suffix (3.0.3) + public_suffix (4.0.1) puma (3.12.0) pundit (2.0.1) activesupport (>= 3.0.0) @@ -518,7 +512,7 @@ GEM execjs railties (>= 3.2) tilt - regexp_parser (1.3.0) + regexp_parser (1.6.0) request_store (1.4.1) rack (>= 1.4) responders (3.0.0) @@ -717,7 +711,7 @@ DEPENDENCIES actiontext! active_link_to active_model_serializers - activestorage-openstack! + activestorage-openstack administrate after_party anchored diff --git a/app/assets/stylesheets/new_design/groupe_instructeur.scss b/app/assets/stylesheets/new_design/groupe_instructeur.scss new file mode 100644 index 000000000..419bf075f --- /dev/null +++ b/app/assets/stylesheets/new_design/groupe_instructeur.scss @@ -0,0 +1,6 @@ +.groupe-instructeur { + .actions { + width: 200px; + text-align: center; + } +} diff --git a/app/assets/stylesheets/new_design/utils.scss b/app/assets/stylesheets/new_design/utils.scss index ef32f9f20..9e5b14290 100644 --- a/app/assets/stylesheets/new_design/utils.scss +++ b/app/assets/stylesheets/new_design/utils.scss @@ -1,4 +1,5 @@ @import "colors"; +@import "constants"; .pull-left { float: left; @@ -48,3 +49,7 @@ background: $orange-bg; color: $black; } + +.mt-2 { + margin-top: 2 * $default-spacer; +} diff --git a/app/controllers/concerns/create_avis_concern.rb b/app/controllers/concerns/create_avis_concern.rb index e7c34495c..81c7dd3a9 100644 --- a/app/controllers/concerns/create_avis_concern.rb +++ b/app/controllers/concerns/create_avis_concern.rb @@ -10,28 +10,41 @@ module CreateAvisConcern # the :emails parameter is a 1-element array. # Hence the call to first # https://github.com/rails/rails/issues/17225 - emails = create_avis_params[:emails].first.split(',').map(&:strip) + expert_emails = create_avis_params[:emails].first.split(',').map(&:strip) + allowed_dossiers = [dossier] + + if create_avis_params[:invite_linked_dossiers].present? + allowed_dossiers += dossier.linked_dossiers + end create_results = Avis.create( - emails.map do |email| - { - email: email, - introduction: create_avis_params[:introduction], - claimant: current_instructeur, - dossier: dossier, - confidentiel: confidentiel - } + expert_emails.flat_map do |email| + allowed_dossiers.map do |dossier| + { + email: email, + introduction: create_avis_params[:introduction], + claimant: current_instructeur, + dossier: dossier, + confidentiel: confidentiel + } + end end ) persisted, failed = create_results.partition(&:persisted?) if persisted.any? - sent_emails_addresses = persisted.map(&:email_to_display).join(", ") - flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses}" + sent_emails_addresses = [] persisted.each do |avis| - dossier.demander_un_avis!(avis) + avis.dossier.demander_un_avis!(avis) + + if avis.dossier == dossier + AvisMailer.avis_invitation(avis).deliver_later + sent_emails_addresses << avis.email_to_display + end end + + flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses.uniq.join(", ")}" end if failed.any? @@ -41,13 +54,13 @@ module CreateAvisConcern # When an error occurs, return the avis back to the controller # to give the user a chance to correct and resubmit - Avis.new(create_avis_params.merge(emails: [failed.map(&:email).join(", ")])) + Avis.new(create_avis_params.merge(emails: [failed.map(&:email).uniq.join(", ")])) else nil end end def create_avis_params - params.require(:avis).permit(:introduction, :confidentiel, emails: []) + params.require(:avis).permit(:introduction, :confidentiel, :invite_linked_dossiers, emails: []) end end diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 9b76145ff..a30a52f7e 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -264,7 +264,7 @@ module Instructeurs end def ensure_ownership! - if !procedure.defaut_groupe_instructeur.instructeurs.include?(current_instructeur) + if !current_instructeur.procedures.include?(procedure) flash[:alert] = "Vous n'avez pas accès à cette démarche" redirect_to root_path end diff --git a/app/controllers/new_administrateur/groupe_instructeurs_controller.rb b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb new file mode 100644 index 000000000..b8923d4a3 --- /dev/null +++ b/app/controllers/new_administrateur/groupe_instructeurs_controller.rb @@ -0,0 +1,145 @@ +module NewAdministrateur + class GroupeInstructeursController < AdministrateurController + ITEMS_PER_PAGE = 25 + + def index + @procedure = procedure + + @groupes_instructeurs = paginated_groupe_instructeurs + end + + def show + @procedure = procedure + @groupe_instructeur = groupe_instructeur + @instructeurs = paginated_instructeurs + end + + def create + @groupe_instructeur = procedure + .groupe_instructeurs + .new(label: label, instructeurs: [current_administrateur.instructeur]) + + if @groupe_instructeur.save + redirect_to procedure_groupe_instructeur_path(procedure, @groupe_instructeur), + notice: "Le groupe d’instructeurs « #{label} » a été créé." + else + @procedure = procedure + @groupes_instructeurs = paginated_groupe_instructeurs + + flash[:alert] = "le nom « #{label} » est déjà pris par un autre groupe." + render :index + end + end + + def update + @groupe_instructeur = groupe_instructeur + + if @groupe_instructeur.update(label: label) + redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur), + notice: "Le nom est à présent « #{label} »." + else + @procedure = procedure + @instructeurs = paginated_instructeurs + + flash[:alert] = "le nom « #{label} » est déjà pris par un autre groupe." + render :show + end + end + + def add_instructeur + @instructeur = Instructeur.find_by(email: instructeur_email) || + create_instructeur(instructeur_email) + + if groupe_instructeur.instructeurs.include?(@instructeur) + flash[:alert] = "L’instructeur « #{instructeur_email} » est déjà dans le groupe." + + else + groupe_instructeur.instructeurs << @instructeur + flash[:notice] = "L’instructeur « #{instructeur_email} » a été affecté au groupe." + GroupeInstructeurMailer + .add_instructeur(groupe_instructeur, @instructeur, current_user.email) + .deliver_later + end + + redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur) + end + + def remove_instructeur + if groupe_instructeur.instructeurs.one? + flash[:alert] = "Suppression impossible : il doit y avoir au moins un instructeur dans le groupe" + + else + @instructeur = Instructeur.find(instructeur_id) + groupe_instructeur.instructeurs.destroy(@instructeur) + flash[:notice] = "L’instructeur « #{@instructeur.email} » a été retiré du groupe." + GroupeInstructeurMailer + .remove_instructeur(groupe_instructeur, @instructeur, current_user.email) + .deliver_later + end + + redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur) + end + + def update_routing_criteria_name + procedure.update!(routing_criteria_name: routing_criteria_name) + + redirect_to procedure_groupe_instructeurs_path(procedure), + notice: "Le libellé est maintenant « #{procedure.routing_criteria_name} »." + end + + private + + def create_instructeur(email) + user = User.create_or_promote_to_instructeur( + email, + SecureRandom.hex, + administrateurs: [current_administrateur] + ) + user.invite! + user.instructeur + end + + def procedure + current_administrateur + .procedures + .includes(:groupe_instructeurs) + .find(params[:procedure_id]) + end + + def groupe_instructeur + procedure.groupe_instructeurs.find(params[:id]) + end + + def instructeur_email + params[:instructeur][:email].strip.downcase + end + + def instructeur_id + params[:instructeur][:id] + end + + def label + params[:groupe_instructeur][:label] + end + + def paginated_groupe_instructeurs + procedure + .groupe_instructeurs + .page(params[:page]) + .per(ITEMS_PER_PAGE) + .order(:label) + end + + def paginated_instructeurs + groupe_instructeur + .instructeurs + .page(params[:page]) + .per(ITEMS_PER_PAGE) + .order(:email) + end + + def routing_criteria_name + params[:procedure][:routing_criteria_name] + end + end +end diff --git a/app/graphql/schema.graphql b/app/graphql/schema.graphql index a9ce16d5a..6b6c5e355 100644 --- a/app/graphql/schema.graphql +++ b/app/graphql/schema.graphql @@ -538,7 +538,7 @@ enum TypeDeChamp { multiple_drop_down_list """ - Nombre entier + Nombre """ number diff --git a/app/helpers/dossier_link_helper.rb b/app/helpers/dossier_link_helper.rb index 1a3986771..93c9d9c16 100644 --- a/app/helpers/dossier_link_helper.rb +++ b/app/helpers/dossier_link_helper.rb @@ -1,7 +1,7 @@ module DossierLinkHelper def dossier_linked_path(user, dossier) if user.is_a?(Instructeur) - if dossier.procedure.defaut_groupe_instructeur.instructeurs.include?(user) + if user.groupe_instructeurs.include?(dossier.groupe_instructeur) instructeur_dossier_path(dossier.procedure, dossier) else avis = dossier.avis.find_by(instructeur: user) diff --git a/app/lib/active_storage/service/ds_proxy_service.rb b/app/lib/active_storage/service/ds_proxy_service.rb deleted file mode 100644 index fe540d591..000000000 --- a/app/lib/active_storage/service/ds_proxy_service.rb +++ /dev/null @@ -1,57 +0,0 @@ -module ActiveStorage - # Wraps an ActiveStorage::Service to route direct upload and direct download URLs through our proxy, - # thus avoiding exposing the storage provider’s URL to our end-users. - class Service::DsProxyService < SimpleDelegator - attr_reader :wrapped - - def self.build(wrapped:, configurator:, **options) - new(wrapped: configurator.build(wrapped)) - end - - def initialize(wrapped:) - @wrapped = wrapped - super(wrapped) - end - - def url(*args) - url = wrapped.url(*args) - publicize(url) - end - - def url_for_direct_upload(*args) - url = wrapped.url_for_direct_upload(*args) - publicize(url) - end - - private - - def object_for(key, &block) - blob_url = url(key) - if block_given? - request = Typhoeus::Request.new(blob_url) - request.on_headers do |response| - if response.code != 200 - raise Fog::OpenStack::Storage::NotFound.new - end - end - request.on_body do |chunk| - yield chunk - end - request.run - else - response = Typhoeus.get(blob_url) - if response.success? - response - else - raise Fog::OpenStack::Storage::NotFound.new - end - end - end - - def publicize(url) - search = %r{^https://[^/]+/v1/AUTH_[a-f0-9]{32}} - replace = 'https://static.demarches-simplifiees.fr' - url.gsub(search, replace) - end - end -end diff --git a/app/mailers/groupe_instructeur_mailer.rb b/app/mailers/groupe_instructeur_mailer.rb new file mode 100644 index 000000000..f5af9dd3c --- /dev/null +++ b/app/mailers/groupe_instructeur_mailer.rb @@ -0,0 +1,25 @@ +class GroupeInstructeurMailer < ApplicationMailer + layout 'mailers/layout' + + def add_instructeur(group, instructeur, current_instructeur_email) + @email = instructeur.email + @group = group + @current_instructeur_email = current_instructeur_email + + subject = "Ajout d’un instructeur dans le groupe \"#{group.label}\"" + + emails = @group.instructeurs.pluck(:email) + mail(bcc: emails, subject: subject) + end + + def remove_instructeur(group, instructeur, current_instructeur_email) + @email = instructeur.email + @group = group + @current_instructeur_email = current_instructeur_email + + subject = "Suppression d’un instructeur dans le groupe \"#{group.label}\"" + + emails = @group.instructeurs.pluck(:email) + mail(bcc: emails, subject: subject) + end +end diff --git a/app/models/avis.rb b/app/models/avis.rb index 5696d6411..f30cec28b 100644 --- a/app/models/avis.rb +++ b/app/models/avis.rb @@ -12,7 +12,6 @@ class Avis < ApplicationRecord before_validation -> { sanitize_email(:email) } before_create :try_to_assign_instructeur - after_create :notify_instructeur default_scope { joins(:dossier) } scope :with_answer, -> { where.not(answer: nil) } @@ -24,6 +23,7 @@ class Avis < ApplicationRecord # The form allows subtmitting avis requests to several emails at once, # hence this virtual attribute. attr_accessor :emails + attr_accessor :invite_linked_dossiers def email_to_display instructeur&.email || email @@ -49,10 +49,6 @@ class Avis < ApplicationRecord private - def notify_instructeur - AvisMailer.avis_invitation(self).deliver_later - end - def try_to_assign_instructeur instructeur = Instructeur.find_by(email: email) if instructeur diff --git a/app/models/champ.rb b/app/models/champ.rb index e64ecd583..f2e40be11 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -11,7 +11,7 @@ class Champ < ApplicationRecord belongs_to :etablissement, dependent: :destroy has_many :champs, -> { ordered }, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy - delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, to: :type_de_champ + delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, :dossier_link?, to: :type_de_champ scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) } scope :public_only, -> { where(private: false) } diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 497ddd0f4..07d5e73fc 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -517,6 +517,10 @@ class Dossier < ApplicationRecord self.individual = Individual.create_from_france_connect(fc_information) end + def linked_dossiers + Dossier.where(id: champs.filter(&:dossier_link?).map(&:value).compact) + end + private def log_dossier_operation(author, operation, subject = nil) diff --git a/app/models/groupe_instructeur.rb b/app/models/groupe_instructeur.rb index e3202bdbd..8ebd1dd27 100644 --- a/app/models/groupe_instructeur.rb +++ b/app/models/groupe_instructeur.rb @@ -4,4 +4,9 @@ class GroupeInstructeur < ApplicationRecord has_many :assign_tos has_many :instructeurs, through: :assign_tos, dependent: :destroy has_many :dossiers + + validates :label, presence: { message: 'doit être renseigné' }, allow_nil: false + validates :label, uniqueness: { scope: :procedure, message: 'existe déjà' } + + before_validation -> { label&.strip! } end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index 6d7903eaa..249c1b6f4 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -157,6 +157,10 @@ class TypeDeChamp < ApplicationRecord type_champ == TypeDeChamp.type_champs.fetch(:repetition) end + def dossier_link? + type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) + end + def public? !private? end diff --git a/app/views/groupe_instructeur_mailer/add_instructeur.html.haml b/app/views/groupe_instructeur_mailer/add_instructeur.html.haml new file mode 100644 index 000000000..578312cb7 --- /dev/null +++ b/app/views/groupe_instructeur_mailer/add_instructeur.html.haml @@ -0,0 +1,11 @@ +%p + Bonjour, + +%p + L’instructeur « #{@email} » a été affecté au groupe « #{@group.label} » par « #{@current_instructeur_email} », en charge de la démarche « #{@group.procedure.libelle} ». + +%p + Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : + = link_to(@group.label, procedure_groupe_instructeur_url(@group.procedure, @group)) + += render partial: "layouts/mailers/signature" diff --git a/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml b/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml new file mode 100644 index 000000000..15a3f15a7 --- /dev/null +++ b/app/views/groupe_instructeur_mailer/remove_instructeur.html.haml @@ -0,0 +1,11 @@ +%p + Bonjour, + +%p + L’instructeur « #{@email} » a été retiré du groupe « #{@group.label} » par « #{@current_instructeur_email} », en charge de la démarche « #{@group.procedure.libelle} ». + +%p + Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe : + = link_to(@group.label, procedure_groupe_instructeur_url(@group.procedure, @group)) + += render partial: "layouts/mailers/signature" diff --git a/app/views/instructeurs/avis/instruction.html.haml b/app/views/instructeurs/avis/instruction.html.haml index b8d5eb2f9..c396cd221 100644 --- a/app/views/instructeurs/avis/instruction.html.haml +++ b/app/views/instructeurs/avis/instruction.html.haml @@ -28,7 +28,7 @@ = f.submit 'Envoyer votre avis', class: 'button send' - if !@dossier.termine? - = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } + = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis), linked_dossiers: @dossier.linked_dossiers, must_be_confidentiel: @avis.confidentiel?, avis: @new_avis } - if @dossier.avis_for(current_instructeur).present? = render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis_for(current_instructeur), avis_seen_at: nil } diff --git a/app/views/instructeurs/dossiers/avis.html.haml b/app/views/instructeurs/dossiers/avis.html.haml index 544f6547d..65d661582 100644 --- a/app/views/instructeurs/dossiers/avis.html.haml +++ b/app/views/instructeurs/dossiers/avis.html.haml @@ -4,7 +4,7 @@ .container - if !@dossier.termine? - = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_dossier_path(@dossier.procedure, @dossier), must_be_confidentiel: false, avis: @avis } + = render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_dossier_path(@dossier.procedure, @dossier), linked_dossiers: @dossier.linked_dossiers, must_be_confidentiel: false, avis: @avis } - if @dossier.avis.present? = render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis, avis_seen_at: @avis_seen_at } diff --git a/app/views/instructeurs/recherche/index.html.haml b/app/views/instructeurs/recherche/index.html.haml index 2ccce0994..635895a60 100644 --- a/app/views/instructeurs/recherche/index.html.haml +++ b/app/views/instructeurs/recherche/index.html.haml @@ -3,7 +3,7 @@ .container .page-title Résultat de la recherche : - = pluralize(@dossiers.count, "dossier trouvé", "dossiers trouvés") + = t('pluralize.dossier_trouve', count: @dossiers.count) - if @dossiers.present? %table.table.dossiers-table.hoverable diff --git a/app/views/instructeurs/shared/avis/_form.html.haml b/app/views/instructeurs/shared/avis/_form.html.haml index a23a30ae2..1588633e3 100644 --- a/app/views/instructeurs/shared/avis/_form.html.haml +++ b/app/views/instructeurs/shared/avis/_form.html.haml @@ -6,6 +6,10 @@ = f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);" = f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true + - if linked_dossiers.present? + = f.check_box :invite_linked_dossiers, value: false + = f.label :invite_linked_dossiers, t('helpers.label.invite_linked_dossiers', count: linked_dossiers.length, ids: linked_dossiers.map(&:id).to_sentence) + .flex.justify-between.align-baseline - if must_be_confidentiel %p.confidentiel.flex diff --git a/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml b/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml index 4429e2052..4f0284677 100644 --- a/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml +++ b/app/views/layouts/left_panels/_left_panel_admin_procedurescontroller_navbar.html.haml @@ -27,14 +27,20 @@ %p.missing-steps (à compléter) %a#onglet-administrateurs{ href: url_for(procedure_administrateurs_path(@procedure)) } - .procedure-list-element{ class: ('active' if active == 'Administrateurs') } + .procedure-list-element Administrateurs - %a#onglet-instructeurs{ href: url_for(admin_procedure_assigns_path(@procedure)) } - .procedure-list-element{ class: ('active' if active == 'Instructeurs') } - Instructeurs - - if @procedure.missing_steps.include?(:instructeurs) - %p.missing-steps (à compléter) + - if !feature_enabled?(:routage) + %a#onglet-instructeurs{ href: url_for(admin_procedure_assigns_path(@procedure)) } + .procedure-list-element{ class: ('active' if active == 'Instructeurs') } + Instructeurs + - if @procedure.missing_steps.include?(:instructeurs) + %p.missing-steps (à compléter) + + - if feature_enabled?(:routage) + %a#onglet-instructeurs{ href: url_for(procedure_groupe_instructeurs_path(@procedure)) } + .procedure-list-element + Groupe d'instructeurs - if !@procedure.locked? %a#onglet-champs{ href: champs_procedure_path(@procedure) } diff --git a/app/views/new_administrateur/groupe_instructeurs/index.html.haml b/app/views/new_administrateur/groupe_instructeurs/index.html.haml new file mode 100644 index 000000000..6f9661a11 --- /dev/null +++ b/app/views/new_administrateur/groupe_instructeurs/index.html.haml @@ -0,0 +1,38 @@ += render partial: 'new_administrateur/breadcrumbs', + locals: { steps: [link_to('Démarches', admin_procedures_path), + link_to(@procedure.libelle, admin_procedure_path(@procedure)), + 'Groupes d’instructeurs'] } + +.container.groupe-instructeur + .card + = form_for @procedure, + url: { action: :update_routing_criteria_name }, + html: { class: 'form' } do |f| + + = f.label :routing_criteria_name do + Libellé du routage + %span.notice Ce texte apparaitra sur le formulaire usager comme le libellé d'une liste + = f.text_field :routing_criteria_name, placeholder: 'Votre ville', required: true + = f.submit 'Renommer', class: 'button primary send' + + .card + .card-title Gestion des Groupes + + = form_for :groupe_instructeur, html: { class: 'form' } do |f| + = f.label :label do + Ajouter un groupe + %span.notice Ce groupe sera un choix de la liste « #{@procedure.routing_criteria_name} » . + = f.text_field :label, placeholder: 'Ville de Bordeaux', required: true + = f.submit 'Ajouter le groupe', class: 'button primary send' + + %table.table.mt-2 + %thead + %tr + %th{ colspan: 2 } Liste des groupes + %tbody + - @groupes_instructeurs.each do |group| + %tr + %td= group.label + %td.actions= link_to "voir", procedure_groupe_instructeur_path(@procedure, group) + + = paginate @groupes_instructeurs diff --git a/app/views/new_administrateur/groupe_instructeurs/show.html.haml b/app/views/new_administrateur/groupe_instructeurs/show.html.haml new file mode 100644 index 000000000..40a3df3f4 --- /dev/null +++ b/app/views/new_administrateur/groupe_instructeurs/show.html.haml @@ -0,0 +1,47 @@ += render partial: 'new_administrateur/breadcrumbs', + locals: { steps: [link_to('Démarches', admin_procedures_path), + link_to(@procedure.libelle, admin_procedure_path(@procedure)), + link_to('Groupes d’instructeurs', procedure_groupe_instructeurs_path(@procedure)), + @groupe_instructeur.label] } + +.container.groupe-instructeur + .rename_form_block + .flex.baseline-start + %h1 Groupe « #{@groupe_instructeur.label} » + + .card.mt-2 + = form_for @groupe_instructeur, + url: procedure_groupe_instructeur_path(@procedure, @groupe_instructeur), + html: { class: 'form' } do |f| + + = f.label :label, 'Nom du groupe' + = f.text_field :label, placeholder: 'Ville de Bordeaux', required: true + = f.submit 'Renommer', class: 'button primary send' + + .card + .card-title Gestion des instructeurs + = form_for :instructeur, + url: { action: :add_instructeur }, + html: { class: 'form' } do |f| + + = f.label :email do + Affecter un nouvel instructeur + = f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true + = f.submit 'Affecter', class: 'button primary send' + + %table.table.mt-2 + %thead + %tr + %th{ colspan: 2 } Instructeurs affectés + %tbody + - @instructeurs.each do |instructeur| + %tr + %td= instructeur.email + %td.actions= button_to 'retirer', + { action: :remove_instructeur }, + { method: :delete, + data: { confirm: "Êtes-vous sûr de vouloir retirer l’instructeur « #{instructeur.email} » du groupe  « #{@groupe_instructeur.label} » ?" }, + params: { instructeur: { id: instructeur.id }}, + class: 'button' } + + = paginate @instructeurs diff --git a/config/environments/production.rb b/config/environments/production.rb index 0c93feb51..8837fb88b 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -93,7 +93,7 @@ Rails.application.configure do # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true - config.active_storage.service = :proxied + config.active_storage.service = :openstack # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb index 7357c9267..1d600d89c 100644 --- a/config/initializers/active_storage.rb +++ b/config/initializers/active_storage.rb @@ -7,3 +7,33 @@ ActiveStorage::Service.url_expires_in = 1.hour # cleaner (as it allows to enqueue the virus scan on attachment creation, rather # than on blob creation). ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner } + +# When an OpenStack service is initialized it makes a request to fetch +# `publicURL` to use for all operations. We intercept the method that reads +# this url and replace the host with DS_Proxy host. This way all the operation +# are performed through DS_Proxy. +# +# https://github.com/fog/fog-openstack/blob/37621bb1d5ca78d037b3c56bd307f93bba022ae1/lib/fog/openstack/auth/catalog/v2.rb#L16 +require 'fog/openstack/auth/catalog/v2' + +module Fog::OpenStack::Auth::Catalog + class V2 + def endpoint_url(endpoint, interface) + url = endpoint["#{interface}URL"] + + if interface == 'public' + publicize(url) + else + url + end + end + + private + + def publicize(url) + search = %r{^https://[^/]+/} + replace = 'https://static.demarches-simplifiees.fr/' + url.gsub(search, replace) + end + end +end diff --git a/config/locales/fr.yml b/config/locales/fr.yml index c44b18967..5935c6cc9 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -313,3 +313,7 @@ fr: zero: archivé one: archivé other: archivés + dossier_trouve: + zero: 0 dossier trouvé + one: 1 dossier trouvé + other: "%{count} dossiers trouvés" diff --git a/config/locales/models/avis/fr.yml b/config/locales/models/avis/fr.yml index 9d1c31792..c458dffd9 100644 --- a/config/locales/models/avis/fr.yml +++ b/config/locales/models/avis/fr.yml @@ -5,3 +5,8 @@ fr: attributes: avis: answer: "Réponse" + helpers: + label: + invite_linked_dossiers: + one: Inviter aussi l'expert sur le dossier lié n° %{ids} + other: Inviter aussi l'expert sur les dossiers liés n° %{ids} diff --git a/config/locales/models/type_de_champ/fr.yml b/config/locales/models/type_de_champ/fr.yml index a76f1ad6b..cf97e106f 100644 --- a/config/locales/models/type_de_champ/fr.yml +++ b/config/locales/models/type_de_champ/fr.yml @@ -9,7 +9,7 @@ fr: textarea: 'Zone de texte' date: 'Date' datetime: 'Date et Heure' - number: 'Nombre entier' + number: 'Nombre' decimal_number: 'Nombre décimal' integer_number: 'Nombre entier' checkbox: 'Case à cocher' diff --git a/config/routes.rb b/config/routes.rb index d60cd0c65..5730cf8ef 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -350,6 +350,17 @@ Rails.application.routes.draw do get 'annotations' end + resources :groupe_instructeurs, only: [:index, :show, :create, :update] do + member do + post 'add_instructeur' + delete 'remove_instructeur' + end + + collection do + patch 'update_routing_criteria_name' + end + end + resources :administrateurs, controller: 'procedure_administrateurs', only: [:index, :create, :destroy] resources :types_de_champ, only: [:create, :update, :destroy] do diff --git a/config/storage.yml b/config/storage.yml index 11de850f6..0427a3f7a 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -4,9 +4,6 @@ local: test: service: Disk root: <%= Rails.root.join("tmp/storage") %> -proxied: - service: DsProxy - wrapped: openstack openstack: service: OpenStack container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>" diff --git a/db/migrate/20191023183120_add_default_value_to_routing_criteria_name.rb b/db/migrate/20191023183120_add_default_value_to_routing_criteria_name.rb new file mode 100644 index 000000000..d9cd9fff7 --- /dev/null +++ b/db/migrate/20191023183120_add_default_value_to_routing_criteria_name.rb @@ -0,0 +1,5 @@ +class AddDefaultValueToRoutingCriteriaName < ActiveRecord::Migration[5.2] + def change + change_column :procedures, :routing_criteria_name, :text, default: "Votre ville" + end +end diff --git a/db/schema.rb b/db/schema.rb index 3736ce621..b4c4e4823 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: 2019_10_14_160538) do +ActiveRecord::Schema.define(version: 2019_10_23_183120) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -486,7 +486,7 @@ ActiveRecord::Schema.define(version: 2019_10_14_160538) do t.string "path", null: false t.string "declarative_with_state" t.text "monavis_embed" - t.text "routing_criteria_name" + t.text "routing_criteria_name", default: "Votre ville" t.boolean "csv_export_queued" t.boolean "xlsx_export_queued" t.boolean "ods_export_queued" diff --git a/spec/controllers/instructeurs/avis_controller_spec.rb b/spec/controllers/instructeurs/avis_controller_spec.rb index bd8bd8821..86e7a5bec 100644 --- a/spec/controllers/instructeurs/avis_controller_spec.rb +++ b/spec/controllers/instructeurs/avis_controller_spec.rb @@ -123,9 +123,10 @@ describe Instructeurs::AvisController, type: :controller do let(:intro) { 'introduction' } let(:created_avis) { Avis.last } let!(:old_avis_count) { Avis.count } + let(:invite_linked_dossiers) { nil } before do - post :create_avis, params: { id: previous_avis.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel } } + post :create_avis, params: { id: previous_avis.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers } } end context 'when an invalid email' do @@ -180,6 +181,34 @@ describe Instructeurs::AvisController, type: :controller do it { expect(created_avis.confidentiel).to be(true) } end end + + context 'with linked dossiers' do + let(:asked_confidentiel) { false } + let(:previous_avis_confidentiel) { false } + let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } + + it do + expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com") + expect(Avis.count).to eq(old_avis_count + 1) + expect(created_avis.email).to eq("a@b.com") + expect(created_avis.dossier).to eq(dossier) + end + + context 'checked' do + let(:invite_linked_dossiers) { true } + let(:created_avis) { Avis.last(2).first } + let(:linked_avis) { Avis.last } + let(:linked_dossier) { dossier.reload.linked_dossiers.first } + + it do + expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com") + expect(Avis.count).to eq(old_avis_count + 2) + expect(created_avis.email).to eq("a@b.com") + expect(created_avis.dossier).to eq(dossier) + expect(linked_avis.dossier).to eq(linked_dossier) + end + end + end end end diff --git a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb new file mode 100644 index 000000000..6b0d388a4 --- /dev/null +++ b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb @@ -0,0 +1,162 @@ +describe NewAdministrateur::GroupeInstructeursController, type: :controller do + render_views + + let(:admin) { create(:administrateur) } + let(:procedure) { create(:procedure, :published, administrateurs: [admin]) } + let!(:gi_1_1) { procedure.defaut_groupe_instructeur } + + let(:procedure2) { create(:procedure, :published) } + let!(:gi_2_2) { procedure2.groupe_instructeurs.create(label: 'groupe instructeur 2 2') } + + before { sign_in(admin.user) } + + describe '#index' do + context 'of a procedure I own' do + let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') } + + before { get :index, params: { procedure_id: procedure.id } } + + context 'when a procedure has multiple groups' do + it { expect(response).to have_http_status(:ok) } + it { expect(response.body).to include(gi_1_1.label) } + it { expect(response.body).to include(gi_1_2.label) } + it { expect(response.body).not_to include(gi_2_2.label) } + end + end + end + + describe '#show' do + context 'of a group I belong to' do + before { get :show, params: { procedure_id: procedure.id, id: gi_1_1.id } } + + it { expect(response).to have_http_status(:ok) } + end + end + + describe '#create' do + before do + post :create, + params: { + procedure_id: procedure.id, + groupe_instructeur: { label: label } + } + end + + context 'with a valid name' do + let(:label) { "nouveau_groupe" } + + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, procedure.groupe_instructeurs.last)) } + it { expect(procedure.groupe_instructeurs.count).to eq(2) } + end + + context 'with an invalid group name' do + let(:label) { gi_1_1.label } + + it { expect(response).to render_template(:index) } + it { expect(procedure.groupe_instructeurs.count).to eq(1) } + it { expect(flash.alert).to be_present } + end + end + + describe '#update' do + let(:new_name) { 'nouveau nom du groupe' } + + before do + patch :update, + params: { + procedure_id: procedure.id, + id: gi_1_1.id, + groupe_instructeur: { label: new_name } + } + end + + it { expect(gi_1_1.reload.label).to eq(new_name) } + it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) } + it { expect(flash.notice).to be_present } + + context 'when the name is already taken' do + let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') } + let(:new_name) { gi_1_2.label } + + it { expect(gi_1_1.reload.label).not_to eq(new_name) } + it { expect(flash.alert).to be_present } + end + end + + describe '#add_instructeur' do + let!(:instructeur) { create(:instructeur) } + before do + gi_1_1.instructeurs << instructeur + + post :add_instructeur, + params: { + procedure_id: procedure.id, + id: gi_1_1.id, + instructeur: { email: new_instructeur_email } + } + end + + context 'of a new instructeur' do + let(:new_instructeur_email) { 'new_instructeur@mail.com' } + + it { expect(gi_1_1.instructeurs.pluck(:email)).to include(new_instructeur_email) } + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) } + end + + context 'of an instructeur already in the group' do + let(:new_instructeur_email) { instructeur.email } + + it { expect(flash.alert).to be_present } + it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur)) } + end + end + + describe '#remove_instructeur' do + let!(:instructeur) { create(:instructeur) } + + before { gi_1_1.instructeurs << admin.instructeur << instructeur } + + def remove_instructeur(email) + delete :remove_instructeur, + params: { + procedure_id: procedure.id, + id: gi_1_1.id, + instructeur: { id: admin.instructeur.id } + } + end + + context 'when there are many instructeurs' do + before { remove_instructeur(admin.user.email) } + + it { expect(gi_1_1.instructeurs).to include(instructeur) } + it { expect(gi_1_1.reload.instructeurs.count).to eq(1) } + it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) } + end + + context 'when there is only one instructeur' do + before do + remove_instructeur(admin.user.email) + remove_instructeur(instructeur.email) + end + + it { expect(gi_1_1.instructeurs).to include(instructeur) } + it { expect(gi_1_1.instructeurs.count).to eq(1) } + it { expect(flash.alert).to eq('Suppression impossible : il doit y avoir au moins un instructeur dans le groupe') } + it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) } + end + end + + describe '#update_routing_criteria_name' do + before do + patch :update_routing_criteria_name, + params: { + procedure_id: procedure.id, + procedure: { routing_criteria_name: 'new name !' } + } + end + + it { expect(procedure.reload.routing_criteria_name).to eq('new name !') } + end +end diff --git a/spec/factories/dossier.rb b/spec/factories/dossier.rb index 163109eac..313b49d40 100644 --- a/spec/factories/dossier.rb +++ b/spec/factories/dossier.rb @@ -54,10 +54,30 @@ FactoryBot.define do trait :with_dossier_link do after(:create) do |dossier, _evaluator| + # create linked dossier linked_dossier = create(:dossier) - type_de_champ = dossier.procedure.types_de_champ.find { |t| t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) } - champ = dossier.champs.find { |c| c.type_de_champ == type_de_champ } + # find first type de champ dossier_link + type_de_champ = dossier.procedure.types_de_champ.find do |t| + t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) + end + + # if type de champ does not exist create it + if !type_de_champ + type_de_champ = create(:type_de_champ_dossier_link, procedure: dossier.procedure) + end + + # find champ with the type de champ + champ = dossier.reload.champs.find do |c| + c.type_de_champ == type_de_champ + end + + # if champ does not exist create it + if !champ + champ = create(:champ_dossier_link, dossier: dossier, type_de_champ: type_de_champ) + end + + # set champ value with linked dossier champ.value = linked_dossier.id champ.save! end diff --git a/spec/features/instructeurs/expert_spec.rb b/spec/features/instructeurs/expert_spec.rb index c938c2f6d..08ceae5a3 100644 --- a/spec/features/instructeurs/expert_spec.rb +++ b/spec/features/instructeurs/expert_spec.rb @@ -8,7 +8,7 @@ feature 'Inviting an expert:' do let(:expert) { create(:instructeur, password: expert_password) } let(:expert_password) { 'mot de passe d’expert' } let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } - let(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) } + let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) } context 'as an Instructeur' do scenario 'I can invite an expert' do @@ -20,6 +20,7 @@ feature 'Inviting an expert:' do fill_in 'avis_emails', with: 'expert1@exemple.fr, expert2@exemple.fr' fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.' + check 'avis_invite_linked_dossiers' page.select 'confidentiel', from: 'avis_confidentiel' perform_enqueued_jobs do @@ -34,6 +35,8 @@ feature 'Inviting an expert:' do expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.') end + expect(Avis.count).to eq(4) + expect(all_emails.size).to eq(2) invitation_email = open_email('expert2@exemple.fr') avis = Avis.find_by(email: 'expert2@exemple.fr') sign_up_link = sign_up_instructeur_avis_path(avis.id, avis.email) diff --git a/spec/features/instructeurs/instruction_spec.rb b/spec/features/instructeurs/instruction_spec.rb index 085b39417..52140f952 100644 --- a/spec/features/instructeurs/instruction_spec.rb +++ b/spec/features/instructeurs/instruction_spec.rb @@ -7,8 +7,7 @@ feature 'Instructing a dossier:' do let!(:instructeur) { create(:instructeur, password: password) } let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) } - let!(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) } - + let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) } context 'the instructeur is also a user' do scenario 'a instructeur can fill a dossier' do visit commencer_path(path: procedure.path) diff --git a/spec/features/routing/full_scenario_spec.rb b/spec/features/routing/full_scenario_spec.rb new file mode 100644 index 000000000..86da5f97c --- /dev/null +++ b/spec/features/routing/full_scenario_spec.rb @@ -0,0 +1,126 @@ +require 'spec_helper' + +feature 'The routing' do + let(:procedure) { create(:procedure, :with_service, :for_individual) } + let(:administrateur) { create(:administrateur, procedures: [procedure]) } + let(:scientifique_user) { create(:user) } + let(:litteraire_user) { create(:user) } + + before { Flipper.enable_actor(:routage, administrateur.user) } + + scenario 'works' do + login_as administrateur.user, scope: :user + + visit admin_procedure_path(procedure.id) + click_on "Groupe d'instructeurs" + + # rename routing criteria to spécialité + fill_in 'procedure_routing_criteria_name', with: 'spécialité' + click_on 'Renommer' + expect(procedure.reload.routing_criteria_name).to eq('spécialité') + + # rename defaut groupe to littéraire + click_on 'voir' + fill_in 'groupe_instructeur_label', with: 'littéraire' + click_on 'Renommer' + + # add victor to littéraire groupe + fill_in 'instructeur_email', with: 'victor@inst.com' + perform_enqueued_jobs { click_on 'Affecter' } + victor = User.find_by(email: 'victor@inst.com').instructeur + + click_on "Groupes d’instructeurs" + + # add scientifique groupe + fill_in 'groupe_instructeur_label', with: 'scientifique' + click_on 'Ajouter le groupe' + + # add marie to scientifique groupe + fill_in 'instructeur_email', with: 'marie@inst.com' + perform_enqueued_jobs { click_on 'Affecter' } + marie = User.find_by(email: 'marie@inst.com').instructeur + + # publish + publish_procedure(procedure) + log_out + + # 2 users fill a dossier in each group + user_send_dossier(scientifique_user, 'scientifique') + user_send_dossier(litteraire_user, 'littéraire') + + # the litteraires instructeurs only manage the litteraires dossiers + register_instructeur_and_log_in(victor.email) + click_on procedure.libelle + expect(page).to have_text(litteraire_user.email) + expect(page).not_to have_text(scientifique_user.email) + + # the search only show litteraires dossiers + fill_in 'q', with: scientifique_user.email + click_on 'Rechercher' + expect(page).to have_text('0 dossier trouvé') + + fill_in 'q', with: litteraire_user.email + click_on 'Rechercher' + expect(page).to have_text('1 dossier trouvé') + + ## and the result is clickable + click_on litteraire_user.email + expect(page).to have_current_path(instructeur_dossier_path(procedure, litteraire_user.dossiers.first)) + + log_out + + # the scientifiques instructeurs only manage the scientifiques dossiers + register_instructeur_and_log_in(marie.email) + click_on procedure.libelle + expect(page).not_to have_text(litteraire_user.email) + expect(page).to have_text(scientifique_user.email) + log_out + + # TODO: notifications tests + end + + def publish_procedure(procedure) + click_on procedure.libelle + find('#publish-procedure').click + within '#publish-modal' do + fill_in 'lien_site_web', with: 'http://some.website' + click_on 'publish' + end + + expect(page).to have_text('Démarche publiée') + end + + def user_send_dossier(user, groupe) + login_as user, scope: :user + visit commencer_path(path: procedure.reload.path) + click_on 'Commencer la démarche' + + fill_in 'individual_nom', with: 'Nom' + fill_in 'individual_prenom', with: 'Prenom' + click_button('Continuer') + + select(groupe, from: 'dossier_groupe_instructeur_id') + + click_on 'Déposer le dossier' + + log_out + end + + def register_instructeur_and_log_in(email) + confirmation_email = emails_sent_to(email) + .filter { |m| m.subject == 'Activez votre compte instructeur' } + .first + token_params = confirmation_email.body.match(/token=[^"]+/) + + visit "users/activate?#{token_params}" + fill_in :user_password, with: 'démarches-simplifiées-pwd' + + click_button 'Définir le mot de passe' + + expect(page).to have_content 'Mot de passe enregistré' + end + + def log_out + click_on 'Se déconnecter' + end +end diff --git a/spec/helpers/dossier_link_helper_spec.rb b/spec/helpers/dossier_link_helper_spec.rb index 2c7db4af1..7b43b37ec 100644 --- a/spec/helpers/dossier_link_helper_spec.rb +++ b/spec/helpers/dossier_link_helper_spec.rb @@ -15,10 +15,11 @@ describe DossierLinkHelper do end context "when access as instructeur" do - let(:dossier) { create(:dossier) } + let(:procedure) { create(:procedure, :routee) } + let(:dossier) { create(:dossier, groupe_instructeur: procedure.groupe_instructeurs.last) } let(:instructeur) { create(:instructeur) } - before { dossier.procedure.defaut_groupe_instructeur.instructeurs << instructeur } + before { procedure.groupe_instructeurs.last.instructeurs << instructeur } it { expect(helper.dossier_linked_path(instructeur, dossier)).to eq(instructeur_dossier_path(dossier.procedure, dossier)) } end diff --git a/spec/models/avis_spec.rb b/spec/models/avis_spec.rb index 4b807644c..74fb242e7 100644 --- a/spec/models/avis_spec.rb +++ b/spec/models/avis_spec.rb @@ -87,18 +87,6 @@ RSpec.describe Avis, type: :model do end end - describe '#notify_instructeur' do - context 'when an avis is created' do - before do - avis_invitation_double = double('avis_invitation', deliver_later: true) - allow(AvisMailer).to receive(:avis_invitation).and_return(avis_invitation_double) - Avis.create(claimant: claimant, email: 'email@l.com') - end - - it { expect(AvisMailer).to have_received(:avis_invitation) } - end - end - describe '#try_to_assign_instructeur' do let!(:instructeur) { create(:instructeur) } let(:avis) { Avis.create(claimant: claimant, email: email, dossier: create(:dossier)) } diff --git a/spec/models/groupe_instructeur_spec.rb b/spec/models/groupe_instructeur_spec.rb new file mode 100644 index 000000000..4a3b3ce62 --- /dev/null +++ b/spec/models/groupe_instructeur_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe GroupeInstructeur, type: :model do + let(:procedure) { create(:procedure) } + subject { GroupeInstructeur.new(label: label, procedure: procedure) } + + context 'with no label provided' do + let(:label) { '' } + + it { is_expected.to be_invalid } + end + + context 'with a valid label' do + let(:label) { 'Préfecture de la Marne' } + + it { is_expected.to be_valid } + end + + context 'with a label with extra spaces' do + let(:label) { 'Préfecture de la Marne ' } + before do + subject.save + subject.reload + end + + it { is_expected.to be_valid } + it { expect(subject.label).to eq("Préfecture de la Marne") } + end + + context 'with a label already used for this procedure' do + let(:label) { 'Préfecture de la Marne' } + before do + GroupeInstructeur.create!(label: label, procedure: procedure) + end + + it { is_expected.to be_invalid } + end +end