diff --git a/app/controllers/instructeurs/dossiers_controller.rb b/app/controllers/instructeurs/dossiers_controller.rb index fc4b82b19..64a10cc6e 100644 --- a/app/controllers/instructeurs/dossiers_controller.rb +++ b/app/controllers/instructeurs/dossiers_controller.rb @@ -331,6 +331,31 @@ module Instructeurs end end + def reaffectation + @dossier = current_instructeur.dossiers.find(params[:dossier_id]) + + @groupe_instructeur = @dossier.groupe_instructeur + + @groupes_instructeurs = Kaminari.paginate_array(@groupe_instructeur.other_groupe_instructeurs) + .page(params[:page]) + .per(ITEMS_PER_PAGE) + end + + def reaffecter + dossier = current_instructeur.dossiers.find(params[:dossier_id]) + + new_group = dossier + .procedure + .groupe_instructeurs.find(params[:groupe_instructeur_id]) + + dossier.assign_to_groupe_instructeur(new_group) + + dossier.update!(forced_groupe_instructeur: true) + + flash.notice = t('instructeurs.dossiers.reaffectation', dossier_id: dossier.id, label: new_group.label) + redirect_to instructeur_procedure_path(procedure) + end + private def dossier_scope diff --git a/app/models/dossier.rb b/app/models/dossier.rb index d44d9d4eb..5337e9215 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -17,6 +17,7 @@ # en_construction_close_to_expiration_notice_sent_at :datetime # en_instruction_at :datetime # for_procedure_preview :boolean default(FALSE) +# forced_groupe_instructeur :boolean # groupe_instructeur_updated_at :datetime # hidden_at :datetime # hidden_by_administration_at :datetime diff --git a/app/models/groupe_instructeur.rb b/app/models/groupe_instructeur.rb index a05a85c7c..185538b1a 100644 --- a/app/models/groupe_instructeur.rb +++ b/app/models/groupe_instructeur.rb @@ -27,9 +27,7 @@ class GroupeInstructeur < ApplicationRecord validates :label, uniqueness: { scope: :procedure } validates :closed, acceptance: { accept: [false] }, if: -> do if closed - other_groupes = procedure.groupe_instructeurs - [self] - - (other_groupes.map(&:closed) + [closed]).all? + (other_groupe_instructeurs.map(&:closed) + [closed]).all? else false end @@ -92,6 +90,10 @@ class GroupeInstructeur < ApplicationRecord !routing_rule_matches_tdc? end + def other_groupe_instructeurs + procedure.groupe_instructeurs - [self] + end + private def routing_rule_matches_tdc? diff --git a/app/models/routing_engine.rb b/app/models/routing_engine.rb index b08cc4bf8..385acda3b 100644 --- a/app/models/routing_engine.rb +++ b/app/models/routing_engine.rb @@ -1,6 +1,7 @@ module RoutingEngine def self.compute(dossier) return if !dossier.procedure.feature_enabled?(:routing_rules) + return if dossier.forced_groupe_instructeur matching_groupe = dossier.procedure.groupe_instructeurs.active.reject(&:routing_to_configure?).find do |gi| gi.routing_rule&.compute(dossier.champs) diff --git a/app/views/instructeurs/dossiers/_header_bottom.html.haml b/app/views/instructeurs/dossiers/_header_bottom.html.haml index 793ae4c8f..17d192f9d 100644 --- a/app/views/instructeurs/dossiers/_header_bottom.html.haml +++ b/app/views/instructeurs/dossiers/_header_bottom.html.haml @@ -22,3 +22,7 @@ = dynamic_tab_item(t('views.instructeurs.dossiers.tab_steps.involved_persons'), personnes_impliquees_instructeur_dossier_path(dossier.procedure, dossier)) + + - if dossier.procedure.routing_enabled? && dossier.procedure.feature_enabled?(:rerouting) + = dynamic_tab_item(t('views.instructeurs.dossiers.tab_steps.reaffectation'), + reaffectation_instructeur_dossier_path(dossier.procedure, dossier)) diff --git a/app/views/instructeurs/dossiers/reaffectation.html.haml b/app/views/instructeurs/dossiers/reaffectation.html.haml new file mode 100644 index 000000000..116191aa0 --- /dev/null +++ b/app/views/instructeurs/dossiers/reaffectation.html.haml @@ -0,0 +1,24 @@ +- content_for(:title, "Réaffectation · Dossier nº #{@dossier.id} (#{@dossier.owner_name})") + += render partial: "header", locals: { dossier: @dossier } + +.container.groupe-instructeur + + .card + .card-title Réaffectation du dossier nº #{@dossier.id} du groupe « #{@groupe_instructeur.label} » + %p + Vous pouvez réaffecter le dossier nº #{@dossier.id} à l'un des groupes d'instructeurs suivants. + %table.table.mt-2 + %thead + %tr + %th{ colspan: 2 }= t("instructeurs.dossiers.existing_groupe", count: @groupes_instructeurs.total_count) + %tbody + - @groupes_instructeurs.each do |group| + %tr + %td= group.label + %td.actions= button_to 'Réaffecter le dossier à ce groupe', + reaffecter_instructeur_dossier_path(procedure_id: @dossier.procedure.id, dossier_id: @dossier.id, groupe_instructeur_id: group.id), + { class: 'button', + data: { confirm: "Êtes-vous sûr de vouloir réaffecter le dossier nº #{@dossier.id} du groupe « #{@groupe_instructeur.label} » vers le groupe  « #{group.label} » ?" } } + + = paginate @groupes_instructeurs, views_prefix: 'shared' diff --git a/app/views/shared/dossiers/_demande.html.haml b/app/views/shared/dossiers/_demande.html.haml index 3cf1acd66..c87b4f940 100644 --- a/app/views/shared/dossiers/_demande.html.haml +++ b/app/views/shared/dossiers/_demande.html.haml @@ -37,3 +37,19 @@ - if champs.any? || dossier.procedure.routing_enabled? .card = render partial: "shared/dossiers/champs", locals: { champs: champs, dossier: dossier, demande_seen_at: demande_seen_at, profile: profile } + + - if dossier.procedure.routing_enabled? + %h2.fr-h6 + Réaffectation + .card + En cas d'erreur de l'usager, vous pouvez réaffecter le dossier vers l'un des groupes d'instructeurs suivants + = form_tag reaffecter_instructeur_dossier_path(procedure_id: @dossier.procedure.id, dossier_id: @dossier.id), + method: :post, + class: 'fr-mb-3w mt-2' do + .flex + = select_tag(:groupe_instructeur_id, + options_for_select(@dossier.groupe_instructeur.other_groupe_instructeurs.pluck(:label, :id)), + include_blank: true, + class: 'fr-select flex auto fr-mr-2w') + + = button_tag 'Réaffecter', class: 'fr-btn fr-btn--secondary' diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index 37de69a0a..3df08aafe 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -18,7 +18,8 @@ features = [ :hide_instructeur_email, :procedure_routage_api, :routing_rules, - :groupe_instructeur_api_hack + :groupe_instructeur_api_hack, + :rerouting ] def database_exists? diff --git a/config/locales/en.yml b/config/locales/en.yml index 1474a4146..e23626a0f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -356,6 +356,7 @@ en: external_opinion: External opinion messaging: Messaging involved_persons: Involved persons + reaffectation: reassignment tab_explainations: a_suivre: No instructor is assigned to follow up on these files. Be the first ! suivis: The folders that are in this tab are only those that you follow. You can exchange with the requester until you can accept them, refuse them or classify them without follow-up. diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 7639a7042..57a1ee46d 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -356,6 +356,7 @@ fr: external_opinion: Avis externes messaging: Messagerie involved_persons: Personnes impliquées + reaffectation: Réaffectation tab_explainations: a_suivre: Aucun instructeur n’est affecté au suivi de ces dossiers. Soyez le premier ! suivis: Les dossiers qui sont dans cet onglet sont uniquement ceux que vous suivez. Vous pouvez échanger avec le demandeur jusqu’à pouvoir les accepter, les refuser ou les classer sans suite. @@ -786,6 +787,10 @@ fr: deleted_by_instructeur: "Le dossier a bien été supprimé de votre interface" impossible_deletion: "Supression impossible : le dossier n’est pas traité" restore: "Le dossier a bien été restauré" + reaffectation: "Le dossier nº %{dossier_id} a été réaffecté au groupe d’instructeurs « %{label} »." + existing_groupe: + one: "%{count} groupe existe" + other: "%{count} groupes existent" labels: to_follow: à suivre total: dossiers diff --git a/config/routes.rb b/config/routes.rb index 4f5efd627..ab883f5fc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -450,6 +450,8 @@ Rails.application.routes.draw do post 'avis' => 'dossiers#create_avis' get 'print' => 'dossiers#print' get 'telecharger_pjs' => 'dossiers#telecharger_pjs' + get 'reaffectation' + post 'reaffecter' end end diff --git a/db/migrate/20230517085816_add_forced_groupe_instructeur_to_dossier.rb b/db/migrate/20230517085816_add_forced_groupe_instructeur_to_dossier.rb new file mode 100644 index 000000000..87820ee38 --- /dev/null +++ b/db/migrate/20230517085816_add_forced_groupe_instructeur_to_dossier.rb @@ -0,0 +1,5 @@ +class AddForcedGroupeInstructeurToDossier < ActiveRecord::Migration[7.0] + def change + add_column :dossiers, :forced_groupe_instructeur, :boolean, default: false, null: false + end +end diff --git a/spec/controllers/instructeurs/dossiers_controller_spec.rb b/spec/controllers/instructeurs/dossiers_controller_spec.rb index 22fa39364..74eeccb38 100644 --- a/spec/controllers/instructeurs/dossiers_controller_spec.rb +++ b/spec/controllers/instructeurs/dossiers_controller_spec.rb @@ -1151,4 +1151,45 @@ describe Instructeurs::DossiersController, type: :controller do it { expect(flash.alert).to eq("Votre action n'a pas été effectuée, ce dossier fait parti d'un traitement de masse.") } end end + + describe '#reaffectation' do + let!(:gi_2) { GroupeInstructeur.create(label: 'deuxième groupe', procedure: procedure) } + let!(:gi_3) { GroupeInstructeur.create(label: 'troisième groupe', procedure: procedure) } + let!(:dossier) { create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first) } + + before do + post :reaffectation, + params: { + procedure_id: procedure.id, + dossier_id: dossier.id + } + end + + it do + expect(response).to have_http_status(:ok) + expect(response.body).to include("Vous pouvez réaffecter le dossier nº #{dossier.id} à l'un des groupes d'instructeurs suivants.") + expect(response.body).to include('2 groupes existent') + end + end + + describe '#reaffecter' do + let!(:gi_2) { GroupeInstructeur.create(label: 'deuxième groupe', procedure: procedure) } + let!(:dossier) { create(:dossier, :en_construction, procedure: procedure, groupe_instructeur: procedure.groupe_instructeurs.first) } + + before do + post :reaffecter, + params: { + procedure_id: procedure.id, + dossier_id: dossier.id, + groupe_instructeur_id: gi_2.id + } + end + + it do + expect(dossier.reload.groupe_instructeur.id).to eq(gi_2.id) + expect(dossier.forced_groupe_instructeur).to be_truthy + expect(response).to redirect_to(instructeur_procedure_path(procedure)) + expect(flash.notice).to eq("Le dossier nº #{dossier.id} a été réaffecté au groupe d’instructeurs « deuxième groupe ».") + end + end end