diff --git a/app/controllers/instructeurs/groupe_instructeurs_controller.rb b/app/controllers/instructeurs/groupe_instructeurs_controller.rb new file mode 100644 index 000000000..0e5a3b7e0 --- /dev/null +++ b/app/controllers/instructeurs/groupe_instructeurs_controller.rb @@ -0,0 +1,98 @@ +module Instructeurs + class GroupeInstructeursController < InstructeurController + 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 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 instructeur_groupe_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 instructeur_groupe_path(procedure, groupe_instructeur) + end + + private + + def create_instructeur(email) + user = User.create_or_promote_to_instructeur( + email, + SecureRandom.hex, + administrateurs: [procedure.administrateurs.first] + ) + user.invite! + user.instructeur + end + + def procedure + current_instructeur + .procedures + .includes(:groupe_instructeurs) + .find(params[:procedure_id]) + end + + def groupe_instructeur + current_instructeur.groupe_instructeurs.find(params[:id]) + end + + def paginated_groupe_instructeurs + current_instructeur + .groupe_instructeurs + .where(procedure: procedure) + .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 instructeur_email + params[:instructeur][:email].strip.downcase + end + + def instructeur_id + params[:instructeur][:id] + end + end +end diff --git a/app/views/instructeurs/groupe_instructeurs/index.html.haml b/app/views/instructeurs/groupe_instructeurs/index.html.haml new file mode 100644 index 000000000..97e62c876 --- /dev/null +++ b/app/views/instructeurs/groupe_instructeurs/index.html.haml @@ -0,0 +1,20 @@ +- content_for(:title, "Notifications pour #{@procedure.libelle}") + += render partial: 'new_administrateur/breadcrumbs', + locals: { steps: [link_to(@procedure.libelle, procedure_path(@procedure)), + 'Groupes d’instructeurs'] } + +.container.groupe-instructeur + .card + .card-title Gestion des Groupes + %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", instructeur_groupe_path(@procedure, group) + + = paginate @groupes_instructeurs diff --git a/app/views/instructeurs/groupe_instructeurs/show.html.haml b/app/views/instructeurs/groupe_instructeurs/show.html.haml new file mode 100644 index 000000000..1fcc742ef --- /dev/null +++ b/app/views/instructeurs/groupe_instructeurs/show.html.haml @@ -0,0 +1,37 @@ +- content_for(:title, "Instructeurs du groupe #{@groupe_instructeur.label}") + += render partial: 'new_administrateur/breadcrumbs', + locals: { steps: [link_to(@procedure.libelle, procedure_path(@procedure)), + link_to('Groupes d’instructeurs', instructeur_groupes_path(@procedure)), + @groupe_instructeur.label] } + +.container.groupe-instructeur + %h1 Groupe « #{@groupe_instructeur.label} » + + .card.mt-2 + .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/app/views/instructeurs/procedures/show.html.haml b/app/views/instructeurs/procedures/show.html.haml index 302ce3a95..229336345 100644 --- a/app/views/instructeurs/procedures/show.html.haml +++ b/app/views/instructeurs/procedures/show.html.haml @@ -13,6 +13,12 @@ | = link_to 'statistiques', stats_instructeur_procedure_path(@procedure), class: 'header-link', data: { turbolinks: false } # Turbolinks disabled for Chartkick. See Issue #350 + - if @procedure.routee? + | + - if current_administrateur.present? && current_administrateur.owns?(@procedure) + = link_to 'instructeurs', procedure_groupe_instructeurs_path(@procedure), class: 'header-link' + - else + = link_to 'instructeurs', instructeur_groupes_path(@procedure), class: 'header-link' %ul.tabs = tab_item('à suivre', diff --git a/app/views/new_administrateur/groupe_instructeurs/show.html.haml b/app/views/new_administrateur/groupe_instructeurs/show.html.haml index 40a3df3f4..393726bd0 100644 --- a/app/views/new_administrateur/groupe_instructeurs/show.html.haml +++ b/app/views/new_administrateur/groupe_instructeurs/show.html.haml @@ -5,9 +5,7 @@ @groupe_instructeur.label] } .container.groupe-instructeur - .rename_form_block - .flex.baseline-start - %h1 Groupe « #{@groupe_instructeur.label} » + %h1 Groupe « #{@groupe_instructeur.label} » .card.mt-2 = form_for @groupe_instructeur, diff --git a/config/routes.rb b/config/routes.rb index 5730cf8ef..16b257416 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -288,6 +288,13 @@ Rails.application.routes.draw do scope module: 'instructeurs', as: 'instructeur' do resources :procedures, only: [:index, :show], param: :procedure_id do member do + resources :groupes, only: [:index, :show], controller: 'groupe_instructeurs' do + member do + post 'add_instructeur' + delete 'remove_instructeur' + end + end + patch 'update_displayed_fields' get 'update_sort/:table/:column' => 'procedures#update_sort', as: 'update_sort' post 'add_filter' diff --git a/spec/controllers/instructeurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/instructeurs/groupe_instructeurs_controller_spec.rb new file mode 100644 index 000000000..c10912227 --- /dev/null +++ b/spec/controllers/instructeurs/groupe_instructeurs_controller_spec.rb @@ -0,0 +1,100 @@ +describe Instructeurs::GroupeInstructeursController, type: :controller do + render_views + + let(:instructeur) { create(:instructeur) } + let(:procedure) { create(:procedure, :published) } + let!(:gi_1_1) { procedure.defaut_groupe_instructeur } + let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') } + + let(:procedure2) { create(:procedure, :published) } + let!(:gi_2_2) { procedure2.groupe_instructeurs.create(label: 'groupe instructeur 2 2') } + + before do + gi_1_2.instructeurs << instructeur + sign_in(instructeur.user) + end + + describe '#index' do + context 'of a procedure I own' do + before do + get :index, params: { procedure_id: procedure.id } + end + + context 'when a procedure has multiple groups' do + it { expect(response).to have_http_status(:ok) } + it { expect(response.body).to include(gi_1_2.label) } + it { expect(response.body).not_to include(gi_1_1.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_2.id } } + + it { expect(response).to have_http_status(:ok) } + end + end + + describe '#add_instructeur' do + before do + post :add_instructeur, + params: { + procedure_id: procedure.id, + id: gi_1_2.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_2.instructeurs.pluck(:email)).to include(new_instructeur_email) } + it { expect(flash.notice).to be_present } + it { expect(response).to redirect_to(instructeur_groupe_path(procedure, gi_1_2)) } + 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(instructeur_groupe_path(procedure, gi_1_2)) } + end + end + + describe '#remove_instructeur' do + let!(:new_instructeur) { create(:instructeur) } + + before { gi_1_1.instructeurs << instructeur << new_instructeur } + + def remove_instructeur(instructeur) + delete :remove_instructeur, + params: { + procedure_id: procedure.id, + id: gi_1_1.id, + instructeur: { id: instructeur.id } + } + end + + context 'when there are many instructeurs' do + before { remove_instructeur(new_instructeur) } + + 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(instructeur_groupe_path(procedure, gi_1_1)) } + end + + context 'when there is only one instructeur' do + before do + remove_instructeur(new_instructeur) + remove_instructeur(instructeur) + 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(instructeur_groupe_path(procedure, gi_1_1)) } + end + end +end diff --git a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb index 6b0d388a4..d13e3d8b4 100644 --- a/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/new_administrateur/groupe_instructeurs_controller_spec.rb @@ -118,17 +118,17 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do before { gi_1_1.instructeurs << admin.instructeur << instructeur } - def remove_instructeur(email) + def remove_instructeur(instructeur) delete :remove_instructeur, params: { procedure_id: procedure.id, id: gi_1_1.id, - instructeur: { id: admin.instructeur.id } + instructeur: { id: instructeur.id } } end context 'when there are many instructeurs' do - before { remove_instructeur(admin.user.email) } + before { remove_instructeur(admin.instructeur) } it { expect(gi_1_1.instructeurs).to include(instructeur) } it { expect(gi_1_1.reload.instructeurs.count).to eq(1) } @@ -137,8 +137,8 @@ describe NewAdministrateur::GroupeInstructeursController, type: :controller do context 'when there is only one instructeur' do before do - remove_instructeur(admin.user.email) - remove_instructeur(instructeur.email) + remove_instructeur(admin.instructeur) + remove_instructeur(instructeur) end it { expect(gi_1_1.instructeurs).to include(instructeur) }