diff --git a/app/controllers/administrateurs/administrateur_controller.rb b/app/controllers/administrateurs/administrateur_controller.rb index f9d3ffdef..5d97768d4 100644 --- a/app/controllers/administrateurs/administrateur_controller.rb +++ b/app/controllers/administrateurs/administrateur_controller.rb @@ -1,12 +1,12 @@ module Administrateurs class AdministrateurController < ApplicationController before_action :authenticate_administrateur! + helper_method :administrateur_as_manager? def retrieve_procedure id = params[:procedure_id] || params[:id] @procedure = current_administrateur.procedures.find(id) - rescue ActiveRecord::RecordNotFound flash.alert = 'Démarche inexistante' redirect_to admin_procedures_path, status: 404 @@ -31,5 +31,20 @@ module Administrateurs @procedure.reset! end end + + def ensure_not_super_admin! + if administrateur_as_manager? + redirect_back fallback_location: root_url, alert: "Interdit aux super admins", status: 403 + end + end + + private + + def administrateur_as_manager? + id = params[:procedure_id] || params[:id] + + current_administrateur.administrateurs_procedures + .exists?(procedure_id: id, manager: true) + end end end diff --git a/app/controllers/administrateurs/archives_controller.rb b/app/controllers/administrateurs/archives_controller.rb index 762bc6dca..e93015927 100644 --- a/app/controllers/administrateurs/archives_controller.rb +++ b/app/controllers/administrateurs/archives_controller.rb @@ -1,6 +1,8 @@ module Administrateurs class ArchivesController < AdministrateurController - before_action :retrieve_procedure, only: [:index, :create] + before_action :retrieve_procedure + before_action :ensure_not_super_admin! + helper_method :create_archive_url def index diff --git a/app/controllers/administrateurs/exports_controller.rb b/app/controllers/administrateurs/exports_controller.rb index 15810e6b6..aaa8750be 100644 --- a/app/controllers/administrateurs/exports_controller.rb +++ b/app/controllers/administrateurs/exports_controller.rb @@ -1,6 +1,7 @@ module Administrateurs class ExportsController < AdministrateurController - before_action :retrieve_procedure, only: [:download] + before_action :retrieve_procedure + before_action :ensure_not_super_admin! def download export = Export.find_or_create_export(export_format, all_groupe_instructeurs, **export_options) diff --git a/app/controllers/administrateurs/groupe_instructeurs_controller.rb b/app/controllers/administrateurs/groupe_instructeurs_controller.rb index fefcc681b..e6a36d2c8 100644 --- a/app/controllers/administrateurs/groupe_instructeurs_controller.rb +++ b/app/controllers/administrateurs/groupe_instructeurs_controller.rb @@ -1,6 +1,9 @@ module Administrateurs class GroupeInstructeursController < AdministrateurController include ActiveSupport::NumberHelper + + before_action :ensure_not_super_admin!, only: [:add_instructeur] + ITEMS_PER_PAGE = 25 CSV_MAX_SIZE = 1.megabytes CSV_ACCEPTED_CONTENT_TYPES = [ diff --git a/app/controllers/administrateurs/procedure_administrateurs_controller.rb b/app/controllers/administrateurs/procedure_administrateurs_controller.rb index 3bc56f546..eea1c45f6 100644 --- a/app/controllers/administrateurs/procedure_administrateurs_controller.rb +++ b/app/controllers/administrateurs/procedure_administrateurs_controller.rb @@ -1,6 +1,7 @@ module Administrateurs class ProcedureAdministrateursController < AdministrateurController before_action :retrieve_procedure, except: [:new] + before_action :ensure_not_super_admin!, only: [:create] def index end diff --git a/app/controllers/instructeurs/archives_controller.rb b/app/controllers/instructeurs/archives_controller.rb index 702047e5a..3f29f20e9 100644 --- a/app/controllers/instructeurs/archives_controller.rb +++ b/app/controllers/instructeurs/archives_controller.rb @@ -1,6 +1,8 @@ module Instructeurs class ArchivesController < InstructeurController - before_action :retrieve_procedure, only: [:index, :create] + before_action :retrieve_procedure + before_action :ensure_not_super_admin! + helper_method :create_archive_url def index diff --git a/app/controllers/instructeurs/instructeur_controller.rb b/app/controllers/instructeurs/instructeur_controller.rb index ac563200d..1c6af5754 100644 --- a/app/controllers/instructeurs/instructeur_controller.rb +++ b/app/controllers/instructeurs/instructeur_controller.rb @@ -5,5 +5,24 @@ module Instructeurs def nav_bar_profile :instructeur end + + def ensure_not_super_admin! + if instructeur_as_manager? + redirect_back fallback_location: root_url, alert: "Interdit aux super admins", status: 403 + end + end + + private + + def instructeur_as_manager? + procedure_id = params[:procedure_id] + + current_instructeur.assign_to + .where(instructeur: current_instructeur, + groupe_instructeur: current_instructeur.groupe_instructeurs.where(procedure_id: procedure_id), + manager: true) + .count + .positive? + end end end diff --git a/app/controllers/instructeurs/procedures_controller.rb b/app/controllers/instructeurs/procedures_controller.rb index 9daf0c80e..0fdfdfa58 100644 --- a/app/controllers/instructeurs/procedures_controller.rb +++ b/app/controllers/instructeurs/procedures_controller.rb @@ -1,6 +1,7 @@ module Instructeurs class ProceduresController < InstructeurController before_action :ensure_ownership!, except: [:index] + before_action :ensure_not_super_admin!, only: [:download_export] ITEMS_PER_PAGE = 25 @@ -60,7 +61,7 @@ module Instructeurs @counts = current_instructeur .dossiers_count_summary(groupe_instructeur_ids) .symbolize_keys - @can_download_dossiers = (@counts[:tous] + @counts[:archives]) > 0 + @can_download_dossiers = (@counts[:tous] + @counts[:archives]) > 0 && !instructeur_as_manager? dossiers = Dossier.where(groupe_instructeur_id: groupe_instructeur_ids) dossiers_count = @counts[statut.underscore.to_sym] @@ -101,7 +102,7 @@ module Instructeurs @a_suivre_count, @suivis_count, @traites_count, @tous_count, @archives_count, @supprimes_recemment_count, @expirant_count = current_instructeur .dossiers_count_summary(groupe_instructeur_ids) .fetch_values('a_suivre', 'suivis', 'traites', 'tous', 'archives', 'supprimes_recemment', 'expirant') - @can_download_dossiers = (@tous_count + @archives_count) > 0 + @can_download_dossiers = (@tous_count + @archives_count) > 0 && !instructeur_as_manager? notifications = current_instructeur.notifications_for_groupe_instructeurs(groupe_instructeur_ids) @has_en_cours_notifications = notifications[:en_cours].present? @@ -145,7 +146,7 @@ module Instructeurs @can_download_dossiers = current_instructeur .dossiers .visible_by_administration - .exists?(groupe_instructeur_id: groupe_instructeur_ids) + .exists?(groupe_instructeur_id: groupe_instructeur_ids) && !instructeur_as_manager? export = Export.find_or_create_export(export_format, groupe_instructeurs, **export_options) diff --git a/app/controllers/manager/procedures_controller.rb b/app/controllers/manager/procedures_controller.rb index 836bfb522..a8058d5c3 100644 --- a/app/controllers/manager/procedures_controller.rb +++ b/app/controllers/manager/procedures_controller.rb @@ -46,23 +46,39 @@ module Manager send_data(emails.join("\n"), :filename => "brouillons-#{procedure.id}-au-#{date}.csv") end - def add_administrateur - add_self = params[:email].blank? - administrateur_email = add_self ? current_super_admin.email : params[:email] - administrateur = Administrateur.by_email(administrateur_email) - if administrateur - AdministrateursProcedure.create(procedure: procedure, administrateur: administrateur, manager: add_self) - if add_self - flash[:notice] = "L’administrateur \"#{administrateur_email}\" est ajouté à la démarche pour la journée." - else - flash[:notice] = "L’administrateur \"#{administrateur_email}\" est ajouté à la démarche." + def add_administrateur_and_instructeur + administrateur = Administrateur.by_email(current_super_admin.email) + instructeur = Instructeur.by_email(current_super_admin.email) + if administrateur && instructeur + ActiveRecord::Base.transaction do + AdministrateursProcedure.create!(procedure: procedure, administrateur: administrateur, manager: true) + procedure.groupe_instructeurs.map do |groupe_instructeur| + instructeur.assign_to.create(groupe_instructeur: groupe_instructeur, manager: true) + end end + + flash[:notice] = "L’administrateur \"#{administrateur.email}\" a été ajouté à la démarche. L'instructeur \"#{instructeur.email}\" a été ajouté aux #{procedure.groupe_instructeurs.count} groupes d'instructeurs" else - if add_self - flash[:alert] = "Vous n’êtes pas connecté en tant qu’administrateur." - else - flash[:alert] = "L’administrateur \"#{administrateur_email}\" est introuvable." - end + flash[:alert] = "L’administrateur \"#{administrateur.email}\" est introuvable." + end + redirect_to manager_procedure_path(procedure) + end + + def add_administrateur_with_confirmation + confirmation_url = confirm_add_administrateur_manager_procedure_url(id: procedure.id, email: current_super_admin.email) + + flash[:notice] = "Veuillez partager ce lien : #{confirmation_url} avec un autre super admin pour que l'operation soit effectuée" + redirect_to manager_procedure_path(procedure) + end + + def confirm_add_administrateur + administrateur_email = params[:email] + if administrateur_email != current_super_admin.email + administrateur = Administrateur.by_email(params[:email]) + AdministrateursProcedure.create!(procedure: procedure, administrateur: administrateur) + flash[:notice] = "L’administrateur \"#{administrateur.email}\" a été ajouté à la démarche." + else + flash[:alert] = "Veuillez partager ce lien avec un autre super administrateur pour qu'il confirme votre action" end redirect_to manager_procedure_path(procedure) end diff --git a/app/jobs/cron/purge_manager_administrateur_sessions_job.rb b/app/jobs/cron/purge_manager_administrateur_sessions_job.rb index 7e20e1b17..b85714dd5 100644 --- a/app/jobs/cron/purge_manager_administrateur_sessions_job.rb +++ b/app/jobs/cron/purge_manager_administrateur_sessions_job.rb @@ -4,5 +4,6 @@ class Cron::PurgeManagerAdministrateurSessionsJob < Cron::CronJob def perform # TODO: add id column to administrateurs_procedures and use destroy_all AdministrateursProcedure.where(manager: true).delete_all + AssignTo.where(manager: true).destroy_all end end diff --git a/app/models/administrateur.rb b/app/models/administrateur.rb index d6bbbe258..0a2a7df23 100644 --- a/app/models/administrateur.rb +++ b/app/models/administrateur.rb @@ -15,7 +15,8 @@ class Administrateur < ApplicationRecord UNUSED_ADMIN_THRESHOLD = 6.months has_and_belongs_to_many :instructeurs - has_and_belongs_to_many :procedures + has_many :administrateurs_procedures + has_many :procedures, through: :administrateurs_procedures has_many :services belongs_to :user @@ -102,7 +103,7 @@ class Administrateur < ApplicationRecord # We can't destroy a service if it has procedures, even if those procedures are archived service.destroy unless service.procedures.with_discarded.any? end - + AdministrateursProcedure.where(administrateur_id: self.id).delete_all destroy end diff --git a/app/models/assign_to.rb b/app/models/assign_to.rb index 44b73cf60..d457f766e 100644 --- a/app/models/assign_to.rb +++ b/app/models/assign_to.rb @@ -6,6 +6,7 @@ # daily_email_notifications_enabled :boolean default(FALSE), not null # instant_email_dossier_notifications_enabled :boolean default(FALSE), not null # instant_email_message_notifications_enabled :boolean default(FALSE), not null +# manager :boolean default(FALSE) # weekly_email_notifications_enabled :boolean default(TRUE), not null # created_at :datetime # updated_at :datetime diff --git a/app/views/administrateurs/archives/index.html.haml b/app/views/administrateurs/archives/index.html.haml index 451cf9b69..61ac49abe 100644 --- a/app/views/administrateurs/archives/index.html.haml +++ b/app/views/administrateurs/archives/index.html.haml @@ -7,6 +7,7 @@ .container %h1.mb-2 Archives + -# index not renderable as administrateur flagged as manager, so render it anyway = render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, export_url: method(:download_admin_procedure_exports_path)) = render partial: "shared/archives/notice" diff --git a/app/views/administrateurs/exports/download.turbo_stream.haml b/app/views/administrateurs/exports/download.turbo_stream.haml index e78737764..4f160d415 100644 --- a/app/views/administrateurs/exports/download.turbo_stream.haml +++ b/app/views/administrateurs/exports/download.turbo_stream.haml @@ -1,3 +1,4 @@ +-# not renderable as administrateur flagged as manager, so render it anyway - if @can_download_dossiers = turbo_stream.update_all '.procedure-actions' do = render Dossiers::ExportComponent.new(procedure: @procedure, exports: @exports, count: @dossiers_count, export_url: method(:admin_procedure_exports_path)) diff --git a/app/views/administrateurs/groupe_instructeurs/_instructeurs.html.haml b/app/views/administrateurs/groupe_instructeurs/_instructeurs.html.haml index 657fb465b..ccd414e93 100644 --- a/app/views/administrateurs/groupe_instructeurs/_instructeurs.html.haml +++ b/app/views/administrateurs/groupe_instructeurs/_instructeurs.html.haml @@ -5,15 +5,20 @@ .instructeur-wrapper - if !procedure.routee? %p.notice Entrez les adresses email des instructeurs que vous souhaitez affecter à cette démarche - = hidden_field_tag :emails, nil - = react_component("ComboMultiple", - options: available_instructeur_emails, selected: [], disabled: [], - group: '.instructeur-wrapper', - name: 'emails', - label: 'Emails', - acceptNewValues: true) - = f.submit 'Affecter', class: 'button primary send' + - if disabled_as_super_admin + = f.select :emails, available_instructeur_emails, {}, disabled: disabled_as_super_admin, id: 'instructeur_emails' + - else + = hidden_field_tag :emails, nil + = react_component("ComboMultiple", + options: available_instructeur_emails, selected: [], disabled: [], + group: '.instructeur-wrapper', + id: 'instructeur_emails', + name: 'emails', + label: 'Emails', + acceptNewValues: true) + + = f.submit 'Affecter', class: 'button primary send', disabled: disabled_as_super_admin %table.table.mt-2 %thead diff --git a/app/views/administrateurs/groupe_instructeurs/_routing.html.haml b/app/views/administrateurs/groupe_instructeurs/_routing.html.haml index 02f5ed9aa..75128b56c 100644 --- a/app/views/administrateurs/groupe_instructeurs/_routing.html.haml +++ b/app/views/administrateurs/groupe_instructeurs/_routing.html.haml @@ -1,6 +1,6 @@ .card - .card-title - = t('.title') + %h2.card-title= t('.title') + - if !procedure.routee? %p.notice= t('.notice_html') @@ -9,16 +9,16 @@ = link_to t('.button.routing_disable'), update_routing_enabled_admin_procedure_groupe_instructeurs_path(procedure, routing: :disable), class: 'button primary mt-1', method: 'patch' - else = link_to t('.button.routing_enable'), update_routing_enabled_admin_procedure_groupe_instructeurs_path(procedure, routing: :enable), class: 'button primary mt-1', method: 'patch' +.card + %h2.card-title L‘autogestion des instructeurs + %p.notice= t('.self_managment_notice_html') - .card-title.mt-4 L‘autogestion des instructeurs - %p.notice= t('.self_managment_notice_html') - - = form_for procedure, - method: :patch, - url: update_instructeurs_self_management_enabled_admin_procedure_groupe_instructeurs_path(procedure), - html: { class: 'form procedure-form__column--form no-background' } do |f| - %label.toggle-switch - = f.check_box :instructeurs_self_management_enabled, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()' - %span.toggle-switch-control.round - %span.toggle-switch-label.on - %span.toggle-switch-label.off + = form_for procedure, + method: :patch, + url: update_instructeurs_self_management_enabled_admin_procedure_groupe_instructeurs_path(procedure), + html: { class: 'form procedure-form__column--form no-background' } do |f| + %label.toggle-switch + = f.check_box :instructeurs_self_management_enabled, class: 'toggle-switch-checkbox', onchange: 'this.form.submit()' + %span.toggle-switch-control.round + %span.toggle-switch-label.on + %span.toggle-switch-label.off diff --git a/app/views/administrateurs/groupe_instructeurs/index.html.haml b/app/views/administrateurs/groupe_instructeurs/index.html.haml index 80cdf9a22..5f1e1e910 100644 --- a/app/views/administrateurs/groupe_instructeurs/index.html.haml +++ b/app/views/administrateurs/groupe_instructeurs/index.html.haml @@ -10,6 +10,8 @@ 'Instructeurs'] } .container.groupe-instructeur + %h1 Gérer les instructeurs et les options d'instruction de « #{@procedure.libelle} » + = render partial: 'administrateurs/groupe_instructeurs/routing', locals: { procedure: @procedure } - if @procedure.routee? @@ -19,4 +21,5 @@ locals: { procedure: @procedure, groupe_instructeur: @procedure.defaut_groupe_instructeur, instructeurs: @instructeurs, - available_instructeur_emails: @available_instructeur_emails } + available_instructeur_emails: @available_instructeur_emails, + disabled_as_super_admin: administrateur_as_manager? } diff --git a/app/views/administrateurs/groupe_instructeurs/show.html.haml b/app/views/administrateurs/groupe_instructeurs/show.html.haml index aefd354ca..4d173134c 100644 --- a/app/views/administrateurs/groupe_instructeurs/show.html.haml +++ b/app/views/administrateurs/groupe_instructeurs/show.html.haml @@ -1,4 +1,3 @@ - = render partial: 'administrateurs/breadcrumbs', locals: { steps: [link_to('Démarches', admin_procedures_path), link_to(@procedure.libelle, admin_procedure_path(@procedure)), @@ -11,4 +10,5 @@ locals: { procedure: @procedure, groupe_instructeur: @groupe_instructeur, instructeurs: @instructeurs, - available_instructeur_emails: @available_instructeur_emails } + available_instructeur_emails: @available_instructeur_emails, + disabled_as_super_admin: administrateur_as_manager? } diff --git a/app/views/administrateurs/procedure_administrateurs/_add_admin_form.html.haml b/app/views/administrateurs/procedure_administrateurs/_add_admin_form.html.haml index 3f744264a..2aa4a8acd 100644 --- a/app/views/administrateurs/procedure_administrateurs/_add_admin_form.html.haml +++ b/app/views/administrateurs/procedure_administrateurs/_add_admin_form.html.haml @@ -1,9 +1,9 @@ = form_for procedure.administrateurs.new(user: User.new), url: { controller: 'procedure_administrateurs' }, - html: { class: 'form', id: "new_administrateur" }, + html: { class: 'form', id: "new_administrateur" }, data: { turbo: true } do |f| = f.label :email do Ajouter un administrateur %p.notice Renseignez l’email d’un administrateur déjà enregistré sur #{APPLICATION_NAME} pour lui permettre de modifier « #{procedure.libelle} ». - = f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true - = f.submit 'Ajouter comme administrateur', class: 'button primary send' + = f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true, disabled: disabled_as_super_admin + = f.submit 'Ajouter comme administrateur', class: 'button primary send', disabled: disabled_as_super_admin diff --git a/app/views/administrateurs/procedure_administrateurs/create.turbo_stream.haml b/app/views/administrateurs/procedure_administrateurs/create.turbo_stream.haml index b8af77c66..6f363a5b3 100644 --- a/app/views/administrateurs/procedure_administrateurs/create.turbo_stream.haml +++ b/app/views/administrateurs/procedure_administrateurs/create.turbo_stream.haml @@ -1,3 +1,3 @@ - if @administrateur.present? = turbo_stream.append "administrateurs", partial: 'administrateur', locals: { procedure: @procedure, administrateur: @administrateur } - = turbo_stream.replace "new_administrateur", partial: 'add_admin_form', locals: { procedure: @procedure } + = turbo_stream.replace "new_administrateur", partial: 'add_admin_form', locals: { procedure: @procedure, disabled_as_super_admin: administrateur_as_manager? } diff --git a/app/views/administrateurs/procedure_administrateurs/index.html.haml b/app/views/administrateurs/procedure_administrateurs/index.html.haml index 9fbccb6bd..dd869fcda 100644 --- a/app/views/administrateurs/procedure_administrateurs/index.html.haml +++ b/app/views/administrateurs/procedure_administrateurs/index.html.haml @@ -4,7 +4,8 @@ 'Administrateurs'], preview: false } .container - %h1 Administrateurs de « #{@procedure.libelle} » + %h1 Gérer les administrateurs de « #{@procedure.libelle} » + %table.table %thead %th= 'Adresse email' @@ -15,4 +16,4 @@ %tfoot %tr %th{ colspan: 4 } - = render 'add_admin_form', procedure: @procedure + = render 'add_admin_form', procedure: @procedure, disabled_as_super_admin: administrateur_as_manager? diff --git a/app/views/instructeurs/procedures/download_export.turbo_stream.haml b/app/views/instructeurs/procedures/download_export.turbo_stream.haml index 137c2f75c..f124bd379 100644 --- a/app/views/instructeurs/procedures/download_export.turbo_stream.haml +++ b/app/views/instructeurs/procedures/download_export.turbo_stream.haml @@ -1,3 +1,4 @@ +-# not renderable as instructeur flagged as manager, so render it anyway - if @can_download_dossiers - if @statut.nil? = turbo_stream.update_all '.procedure-actions' do diff --git a/app/views/manager/procedures/show.html.erb b/app/views/manager/procedures/show.html.erb index 06c550bdb..2fe9d0247 100644 --- a/app/views/manager/procedures/show.html.erb +++ b/app/views/manager/procedures/show.html.erb @@ -64,17 +64,19 @@ as well as a link to its edit page.
<%= render_field attribute, page: page %> <% if attribute.name == 'administrateurs' %> - <%= form_tag(add_administrateur_manager_procedure_path(procedure), style: 'margin-top: 1rem;') do %> + <%= form_tag(add_administrateur_with_confirmation_manager_procedure_path(procedure), style: 'margin-top: 1rem;') do %> <%= email_field_tag(:email, '', placeholder: 'Email', autocapitalize: 'off', autocorrect: 'off', spellcheck: 'false', style: 'margin-bottom: 1rem;width:24rem;') %> - + +

J'utilise cette option ETQ support quand un usager a besoin de devenir administrateur sur une démarche

<% end %> <% if procedure.administrateurs.find { |admin| admin.email == current_super_admin.email } %>

Vous êtes déjà administrateur sur cette démarche

<%= link_to 'Me retirer de cette démarche', delete_administrateur_manager_procedure_path(procedure), method: :put, class: 'button' %> - <% else %> - <%= form_tag(add_administrateur_manager_procedure_path(procedure), style: 'margin-top: 1rem;') do %> - + <%= form_tag(add_administrateur_and_instructeur_manager_procedure_path(procedure), style: 'margin-top: 1rem;') do %> + +

J'utilise cette option temporairement à des fins de support.

+ <% end %> <% end %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 6184da693..5a8d95ee4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,8 +15,10 @@ Rails.application.routes.draw do post 'draft', on: :member post 'discard', on: :member post 'restore', on: :member - post 'add_administrateur', on: :member put 'delete_administrateur', on: :member + post 'add_administrateur_and_instructeur', on: :member + post 'add_administrateur_with_confirmation', on: :member + get 'confirm_add_administrateur', on: :member post 'change_piece_justificative_template', on: :member get 'export_mail_brouillons', on: :member end diff --git a/db/migrate/20220712141913_add_manager_to_assign_tos.rb b/db/migrate/20220712141913_add_manager_to_assign_tos.rb new file mode 100644 index 000000000..06d644572 --- /dev/null +++ b/db/migrate/20220712141913_add_manager_to_assign_tos.rb @@ -0,0 +1,10 @@ +class AddManagerToAssignTos < ActiveRecord::Migration[6.1] + def up + add_column :assign_tos, :manager, :boolean + change_column_default :assign_tos, :manager, false + end + + def down + remove_column :assign_tos, :manager + end +end diff --git a/db/migrate/20220712141945_backfill_assign_tos_manager.rb b/db/migrate/20220712141945_backfill_assign_tos_manager.rb new file mode 100644 index 000000000..805ea367c --- /dev/null +++ b/db/migrate/20220712141945_backfill_assign_tos_manager.rb @@ -0,0 +1,10 @@ +class BackfillAssignTosManager < ActiveRecord::Migration[6.1] + disable_ddl_transaction! + + def change + AssignTo.in_batches do |relation| + relation.update_all manager: false + sleep(0.01) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 45a4b1464..7157e7fe5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -112,6 +112,7 @@ ActiveRecord::Schema.define(version: 2022_07_28_084804) do t.boolean "instant_email_dossier_notifications_enabled", default: false, null: false t.boolean "instant_email_message_notifications_enabled", default: false, null: false t.integer "instructeur_id" + t.boolean "manager", default: false t.datetime "updated_at" t.boolean "weekly_email_notifications_enabled", default: true, null: false t.index ["groupe_instructeur_id", "instructeur_id"], name: "unique_couple_groupe_instructeur_instructeur", unique: true diff --git a/spec/controllers/administrateurs/archives_controller_spec.rb b/spec/controllers/administrateurs/archives_controller_spec.rb index fd0fe767e..659393f27 100644 --- a/spec/controllers/administrateurs/archives_controller_spec.rb +++ b/spec/controllers/administrateurs/archives_controller_spec.rb @@ -1,6 +1,7 @@ describe Administrateurs::ArchivesController, type: :controller do let(:admin) { create(:administrateur) } - let(:procedure) { create :procedure, administrateur: admin, groupe_instructeurs: [groupe_instructeur1, groupe_instructeur2] } + let(:procedure) { create :procedure, groupe_instructeurs: [groupe_instructeur1, groupe_instructeur2] } + let(:administrateur_procedure) { create(:administrateurs_procedure, procedure: procedure, administrateur: admin, manager: manager) } let(:groupe_instructeur1) { create(:groupe_instructeur) } let(:groupe_instructeur2) { create(:groupe_instructeur) } @@ -10,8 +11,11 @@ describe Administrateurs::ArchivesController, type: :controller do context 'when logged out' do it { is_expected.to have_http_status(302) } end - context 'when logged in' do + + context 'when logged in as administrateur_procedure.manager=false' do + let(:manager) { false } before do + administrateur_procedure sign_in(admin.user) end @@ -22,15 +26,30 @@ describe Administrateurs::ArchivesController, type: :controller do subject end end + context 'when logged in as administrateur_procedure.manager=true' do + let(:manager) { true } + + before do + administrateur_procedure + sign_in(admin.user) + end + + it { is_expected.to have_http_status(403) } + end end + describe 'GET #create' do subject { post :create, params: { procedure_id: procedure.id, month: '22-06', type: 'monthly' } } context 'when logged out' do it { is_expected.to have_http_status(302) } end - context 'when logged in' do + + context 'when logged in in as administrateur_procedure.manager=false' do + let(:manager) { false } + before do + administrateur_procedure sign_in(admin.user) end @@ -39,5 +58,19 @@ describe Administrateurs::ArchivesController, type: :controller do expect { subject }.to have_enqueued_job(ArchiveCreationJob).with(procedure, an_instance_of(Archive), admin) end end + + context 'when logged in in as administrateur_procedure.manager=true' do + let(:manager) { true } + + before do + administrateur_procedure + sign_in(admin.user) + end + + it { is_expected.to have_http_status(403) } + it 'does not enqueue the creation job' do + expect { subject }.not_to have_enqueued_job(ArchiveCreationJob) + end + end end end diff --git a/spec/controllers/administrateurs/exports_controller_spec.rb b/spec/controllers/administrateurs/exports_controller_spec.rb index af8f4e5b2..fdcf627e5 100644 --- a/spec/controllers/administrateurs/exports_controller_spec.rb +++ b/spec/controllers/administrateurs/exports_controller_spec.rb @@ -63,5 +63,22 @@ describe Administrateurs::ExportsController, type: :controller do end end end + + context 'when admin is allowed present as manager' do + let!(:procedure) { create(:procedure) } + let!(:administrateur_procedure) { create(:administrateurs_procedure, procedure: procedure, administrateur: administrateur, manager: true) } + + context 'get #index.html' do + it { is_expected.to have_http_status(:forbidden) } + end + context 'get #index.turbo_stream' do + it 'is forbidden' do + post :download, + params: { export_format: :csv, procedure_id: procedure.id }, + format: :turbo_stream + expect(response).to have_http_status(:forbidden) + end + end + end end end diff --git a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb index a8ddcc97b..cc2f34ceb 100644 --- a/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb +++ b/spec/controllers/administrateurs/groupe_instructeurs_controller_spec.rb @@ -208,10 +208,11 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do end describe '#add_instructeur_procedure_non_routee' do - let(:procedure) { create :procedure, administrateur: admin } + let(:procedure) { create :procedure } + let!(:groupe_instructeur) { create(:administrateurs_procedure, procedure: procedure, administrateur: admin, manager: manager) } let(:emails) { ['instructeur_3@ministere_a.gouv.fr', 'instructeur_4@ministere_b.gouv.fr'].to_json } subject { post :add_instructeur, params: { emails: emails, procedure_id: procedure.id, id: gi_1_1.id } } - + let(:manager) { false } context 'when all emails are valid' do let(:emails) { ['test@b.gouv.fr', 'test2@b.gouv.fr'].to_json } it { expect(response.status).to eq(200) } @@ -233,18 +234,17 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do it { expect(subject.request.flash[:alert]).to be_present } it { expect(subject).to redirect_to admin_procedure_groupe_instructeurs_path(procedure) } end + + context 'when signed in admin comes from manager' do + let(:manager) { true } + it { is_expected.to have_http_status(:forbidden) } + end end describe '#add_instructeur' do let!(:instructeur) { create(:instructeur) } let(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') } - - before do - gi_1_2.instructeurs << instructeur - - allow(GroupeInstructeurMailer).to receive(:add_instructeurs) - .and_return(double(deliver_later: true)) - + let(:do_request) do post :add_instructeur, params: { procedure_id: procedure.id, @@ -252,10 +252,16 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do emails: new_instructeur_emails.to_json } end + before do + gi_1_2.instructeurs << instructeur + + allow(GroupeInstructeurMailer).to receive(:add_instructeurs) + .and_return(double(deliver_later: true)) + end context 'of a news instructeurs' do let(:new_instructeur_emails) { ['new_i1@mail.com', 'new_i2@mail.com'] } - + before { do_request } it { expect(gi_1_2.instructeurs.pluck(:email)).to include(*new_instructeur_emails) } it { expect(flash.notice).to be_present } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) } @@ -271,22 +277,32 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do context 'of an instructeur already in the group' do let(:new_instructeur_emails) { [instructeur.email] } - + before { do_request } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) } end context 'of badly formed email' do let(:new_instructeur_emails) { ['badly_formed_email'] } - + before { do_request } it { expect(flash.alert).to be_present } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) } end context 'of an empty string' do let(:new_instructeur_emails) { [''] } - + before { do_request } it { expect(response).to redirect_to(admin_procedure_groupe_instructeur_path(procedure, gi_1_2)) } end + + context 'when connected as an administrateur from manager' do + let(:new_instructeur_emails) { [instructeur.email] } + before do + admin.administrateurs_procedures.update_all(manager: true) + do_request + end + + it { expect(response).to have_http_status(:forbidden) } + end end describe '#remove_instructeur' do diff --git a/spec/controllers/administrateurs/procedure_administrateurs_controller_spec.rb b/spec/controllers/administrateurs/procedure_administrateurs_controller_spec.rb index 15122c393..1f277b2fa 100644 --- a/spec/controllers/administrateurs/procedure_administrateurs_controller_spec.rb +++ b/spec/controllers/administrateurs/procedure_administrateurs_controller_spec.rb @@ -1,14 +1,24 @@ describe Administrateurs::ProcedureAdministrateursController, type: :controller do let(:signed_in_admin) { create(:administrateur) } let(:other_admin) { create(:administrateur) } - let(:procedure) { create(:procedure, administrateurs: [signed_in_admin, other_admin]) } + let!(:administrateurs_procedure) { create(:administrateurs_procedure, administrateur: signed_in_admin, procedure: procedure, manager: manager) } + let!(:procedure) { create(:procedure, administrateurs: [other_admin]) } render_views before do sign_in(signed_in_admin.user) end + describe '#create' do + context 'as manager' do + let(:manager) { true } + subject { post :create, params: { procedure_id: procedure.id, administrateur: { email: create(:administrateur).email } }, format: :turbo_stream } + it { is_expected.to have_http_status(:forbidden) } + end + end + describe '#destroy' do + let(:manager) { false } subject do delete :destroy, params: { procedure_id: procedure.id, id: admin_to_remove.id }, format: :turbo_stream end diff --git a/spec/controllers/instructeurs/archives_controller_spec.rb b/spec/controllers/instructeurs/archives_controller_spec.rb index e3125d4a5..997e9a569 100644 --- a/spec/controllers/instructeurs/archives_controller_spec.rb +++ b/spec/controllers/instructeurs/archives_controller_spec.rb @@ -1,16 +1,15 @@ describe Instructeurs::ArchivesController, type: :controller do - let(:procedure1) { create(:procedure, :published, groupe_instructeurs: [gi1]) } + let(:procedure1) { create(:procedure, :published, groupe_instructeurs: [assign_to.groupe_instructeur]) } let(:procedure2) { create(:procedure, :published, groupe_instructeurs: [gi2]) } - let!(:instructeur) { create(:instructeur, groupe_instructeurs: [gi1, gi2]) } - let!(:archive1) { create(:archive, :generated, groupe_instructeurs: [gi1]) } + let!(:instructeur) { create(:instructeur, groupe_instructeurs: [gi2]) } + let!(:archive1) { create(:archive, :generated, groupe_instructeurs: [assign_to.groupe_instructeur]) } let!(:archive2) { create(:archive, :generated, groupe_instructeurs: [gi2]) } - let(:gi1) { create(:groupe_instructeur) } + let!(:assign_to) { create(:assign_to, instructeur: instructeur, groupe_instructeur: build(:groupe_instructeur), manager: manager) } let(:gi2) { create(:groupe_instructeur) } before do sign_in(instructeur.user) end - after { Timecop.return } describe '#index' do @@ -20,24 +19,50 @@ describe Instructeurs::ArchivesController, type: :controller do create_dossier_for_month(procedure1, 2021, 2) Timecop.freeze(Time.zone.local(2021, 3, 5)) end + subject { get :index, params: { procedure_id: procedure1.id } } - it 'displays archives' do - get :index, params: { procedure_id: procedure1.id } + context 'signed in not as manager' do + let(:manager) { false } - expect(assigns(:archives)).to eq([archive1]) + it { is_expected.to have_http_status(:success) } + it 'assigns archives' do + subject + expect(assigns(:archives)).to eq([archive1]) + end + end + + context 'signed in as manager' do + let(:manager) { true } + + before do + sign_in(instructeur.user) + end + + it { is_expected.to have_http_status(:forbidden) } end end describe '#create' do - let(:month) { '21-03' } - let(:date_month) { Date.strptime(month, "%Y-%m") } let(:subject) do post :create, params: { procedure_id: procedure1.id, type: 'monthly', month: month } end - it "performs archive creation job" do - expect { subject }.to have_enqueued_job(ArchiveCreationJob).with(procedure1, an_instance_of(Archive), instructeur) - expect(flash.notice).to include("Votre demande a été prise en compte") + let(:month) { '21-03' } + let(:date_month) { Date.strptime(month, "%Y-%m") } + + context 'signed in not as manager' do + let(:manager) { false } + + it "performs archive creation job" do + expect { subject }.to have_enqueued_job(ArchiveCreationJob).with(procedure1, an_instance_of(Archive), instructeur) + expect(flash.notice).to include("Votre demande a été prise en compte") + end + end + + context 'signed in as manager' do + let(:manager) { true } + + it { is_expected.to have_http_status(:forbidden) } end end diff --git a/spec/controllers/instructeurs/procedures_controller_spec.rb b/spec/controllers/instructeurs/procedures_controller_spec.rb index ffd65d8e5..b670416d3 100644 --- a/spec/controllers/instructeurs/procedures_controller_spec.rb +++ b/spec/controllers/instructeurs/procedures_controller_spec.rb @@ -468,9 +468,10 @@ describe Instructeurs::ProceduresController, type: :controller do describe '#download_export' do let(:instructeur) { create(:instructeur) } let!(:procedure) { create(:procedure) } - let!(:gi_0) { procedure.defaut_groupe_instructeur } + let!(:assign_to) { create(:assign_to, instructeur: instructeur, groupe_instructeur: build(:groupe_instructeur, procedure: procedure), manager: manager) } + let!(:gi_0) { assign_to.groupe_instructeur } let!(:gi_1) { create(:groupe_instructeur, label: 'gi_1', procedure: procedure, instructeurs: [instructeur]) } - + let(:manager) { false } before { sign_in(instructeur.user) } subject do @@ -498,7 +499,7 @@ describe Instructeurs::ProceduresController, type: :controller do end context 'when the export is ready' do - let(:export) { create(:export, groupe_instructeurs: [gi_1], job_status: 'generated') } + let(:export) { create(:export, groupe_instructeurs: [gi_1, gi_0], job_status: 'generated') } before do export.file.attach(io: StringIO.new('export'), filename: 'file.csv') @@ -511,7 +512,7 @@ describe Instructeurs::ProceduresController, type: :controller do end context 'when another export is ready' do - let(:export) { create(:export, groupe_instructeurs: [gi_0, gi_1]) } + let(:export) { create(:export, groupe_instructeurs: [gi_0]) } before do export.file.attach(io: StringIO.new('export'), filename: 'file.csv') @@ -535,6 +536,11 @@ describe Instructeurs::ProceduresController, type: :controller do expect(response).to have_http_status(:ok) end end + + context 'when logged in through super admin' do + let(:manager) { true } + it { is_expected.to have_http_status(:forbidden) } + end end describe '#create_multiple_commentaire' do diff --git a/spec/factories/administrateurs_procedure.rb b/spec/factories/administrateurs_procedure.rb new file mode 100644 index 000000000..5a12b3d42 --- /dev/null +++ b/spec/factories/administrateurs_procedure.rb @@ -0,0 +1,6 @@ +FactoryBot.define do + factory :administrateurs_procedure do + association :administrateur + association :procedure + end +end diff --git a/spec/models/administrateur_spec.rb b/spec/models/administrateur_spec.rb index 9da4b2c0c..162da0bd6 100644 --- a/spec/models/administrateur_spec.rb +++ b/spec/models/administrateur_spec.rb @@ -3,7 +3,6 @@ describe Administrateur, type: :model do describe 'associations' do it { is_expected.to have_and_belong_to_many(:instructeurs) } - it { is_expected.to have_and_belong_to_many(:procedures) } end describe "#renew_api_token" do diff --git a/spec/system/administrateurs/procedure_administrateurs_spec.rb b/spec/system/administrateurs/procedure_administrateurs_spec.rb new file mode 100644 index 000000000..5a3988e6b --- /dev/null +++ b/spec/system/administrateurs/procedure_administrateurs_spec.rb @@ -0,0 +1,49 @@ +require 'system/administrateurs/procedure_spec_helper' + +describe 'Administrateurs can manage administrateurs', js: true do + include ProcedureSpecHelper + + let(:administrateur) { create(:administrateur) } + let!(:procedure) { create(:procedure) } + let!(:administrateurs_procedure) { create(:administrateurs_procedure, administrateur: administrateur, procedure: procedure, manager: manager) } + let(:manager) { false } + before do + login_as administrateur.user, scope: :user + end + + scenario 'card is clickable' do + visit admin_procedure_path(procedure) + find('#administrateurs').click + expect(page).to have_css("h1", text: "Gérer les administrateurs de « #{procedure.libelle} »") + end + + context 'as admin not flagged from manager' do + let(:manager) { false } + + scenario 'the administrator can add another administrator' do + another_administrateur = create(:administrateur) + visit admin_procedure_administrateurs_path(procedure) + find('#administrateurs').click + + fill_in('administrateur_email', with: another_administrateur.email) + + click_on 'Ajouter comme administrateur' + + within('.alert-success') do + expect(page).to have_content(another_administrateur.email) + end + end + end + + context 'as admin flagged from manager' do + let(:manager) { true } + scenario 'the administrator from manager can not add another administrator' do + administrateur.administrateurs_procedures.update_all(manager: true) + visit admin_procedure_administrateurs_path(procedure) + + find('#administrateurs').click + + expect(page).to have_css("#administrateur_email[disabled=\"disabled\"]") + end + end +end diff --git a/spec/system/administrateurs/procedure_groupe_instructeur_spec.rb b/spec/system/administrateurs/procedure_groupe_instructeur_spec.rb new file mode 100644 index 000000000..34849d318 --- /dev/null +++ b/spec/system/administrateurs/procedure_groupe_instructeur_spec.rb @@ -0,0 +1,46 @@ +require 'system/administrateurs/procedure_spec_helper' + +describe 'Manage procedure instructeurs', js: true do + include ProcedureSpecHelper + + let(:administrateur) { create(:administrateur) } + let!(:procedure) { create(:procedure) } + let!(:administrateurs_procedure) { create(:administrateurs_procedure, administrateur: administrateur, procedure: procedure, manager: manager) } + let(:manager) { false } + before do + login_as administrateur.user, scope: :user + end + + context 'is accessible via card' do + let(:manager) { false } + + scenario 'it works' do + visit admin_procedure_path(procedure) + find('#groupe-instructeurs').click + expect(page).to have_css("h1", text: "Gérer les instructeurs et les options d'instruction de « #{procedure.libelle} »") + end + end + + context 'as admin not from manager' do + let(:manager) { false } + + scenario 'can add instructeur' do + visit admin_procedure_groupe_instructeurs_path(procedure) + + expect { + fill_in "instructeur_emails", with: create(:instructeur).email + click_on "Affecter" + }.to change { procedure.instructeurs.count }.by(1) + end + end + + context 'as admin from manager' do + let(:manager) { true } + + scenario 'cannot add instructeur' do + visit admin_procedure_groupe_instructeurs_path(procedure) + + expect(page).to have_css("#instructeur_emails[disabled=\"disabled\"]") + end + end +end diff --git a/spec/system/administrateurs/procedure_update_spec.rb b/spec/system/administrateurs/procedure_update_spec.rb index b33599eda..ffb1c159d 100644 --- a/spec/system/administrateurs/procedure_update_spec.rb +++ b/spec/system/administrateurs/procedure_update_spec.rb @@ -58,18 +58,4 @@ describe 'Administrateurs can edit procedures', js: true do expect(page).to have_selector('.breadcrumbs li', text: 'Ma petite démarche') end end - - scenario 'the administrator can add another administrator' do - another_administrateur = create(:administrateur) - visit admin_procedure_path(procedure) - find('#administrateurs').click - - fill_in('administrateur_email', with: another_administrateur.email) - - click_on 'Ajouter comme administrateur' - - within('.alert-success') do - expect(page).to have_content(another_administrateur.email) - end - end end