Merge pull request #6534 from betagouv/main

2021-10-11-01
This commit is contained in:
LeSim 2021-10-11 10:20:46 +02:00 committed by GitHub
commit d3f8ba2516
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 380 additions and 37 deletions

View file

@ -47,12 +47,12 @@ module Manager
end end
def add_administrateur def add_administrateur
administrateur = Administrateur.by_email(params[:email]) administrateur = Administrateur.by_email(current_super_admin.email)
if administrateur if administrateur
procedure.administrateurs << administrateur AdministrateursProcedure.create(procedure: procedure, administrateur: administrateur, manager: true)
flash[:notice] = "L'administrateur \"#{params[:email]}\" est ajouté à la démarche." flash[:notice] = "Ladministrateur \"#{administrateur.email}\" est ajouté à la démarche pour la journée."
else else
flash[:alert] = "L'administrateur \"#{params[:email]}\" est introuvable." flash[:alert] = "Vous nêtes pas connecté en tant quadministrateur."
end end
redirect_to manager_procedure_path(procedure) redirect_to manager_procedure_path(procedure)
end end

View file

@ -2,14 +2,37 @@ module Manager
class UsersController < Manager::ApplicationController class UsersController < Manager::ApplicationController
def update def update
user = User.find(params[:id]) user = User.find(params[:id])
new_email = params[:user][:email] preexisting_user = User.find_by(email: targeted_email)
if user.administrateur.present?
flash[:error] = "« #{targeted_email} » est un administrateur. On ne sait pas encore faire."
elsif preexisting_user.nil?
user.skip_reconfirmation! user.skip_reconfirmation!
user.update(email: new_email) user.update(email: targeted_email)
if (user.valid?) if (user.valid?)
flash[:notice] = "L'email a été modifié en « #{new_email} » sans notification ni validation par email." flash[:notice] = "L'email a été modifié en « #{targeted_email} » sans notification ni validation par email."
else else
flash[:error] = "« #{new_email} » nest pas une adresse valide." flash[:error] = user.errors.full_messages.to_sentence
end end
else
user.dossiers.update_all(user_id: preexisting_user.id)
if preexisting_user.instructeur.nil?
user.instructeur&.update(user: preexisting_user)
else
preexisting_user.instructeur.merge(user.instructeur)
end
if preexisting_user.expert.nil?
user.expert&.update(user: preexisting_user)
else
preexisting_user.expert.merge(user.expert)
end
flash[:notice] = "Le compte « #{targeted_email} » a absorbé le compte « #{user.email} »."
end
redirect_to edit_manager_user_path(user) redirect_to edit_manager_user_path(user)
end end
@ -72,5 +95,11 @@ module Manager
end end
redirect_to emails_manager_user_path(@user) redirect_to emails_manager_user_path(@user)
end end
private
def targeted_email
params[:user][:email]
end
end end
end end

View file

@ -0,0 +1,7 @@
class Cron::PurgeManagerAdministrateurSessionsJob < Cron::CronJob
self.schedule_expression = "every day at 3 am"
def perform
AdministrateursProcedure.where(manager: true).destroy_all
end
end

View file

@ -0,0 +1,13 @@
# == Schema Information
#
# Table name: administrateurs_instructeurs
#
# created_at :datetime
# updated_at :datetime
# administrateur_id :integer
# instructeur_id :integer
#
class AdministrateursInstructeur < ApplicationRecord
belongs_to :administrateur
belongs_to :instructeur
end

View file

@ -2,6 +2,7 @@
# #
# Table name: administrateurs_procedures # Table name: administrateurs_procedures
# #
# manager :boolean
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# administrateur_id :bigint not null # administrateur_id :bigint not null

View file

@ -9,8 +9,10 @@
class Expert < ApplicationRecord class Expert < ApplicationRecord
has_one :user has_one :user
has_many :experts_procedures has_many :experts_procedures
has_many :procedures, through: :experts_procedures
has_many :avis, through: :experts_procedures has_many :avis, through: :experts_procedures
has_many :dossiers, through: :avis has_many :dossiers, through: :avis
has_many :commentaires
default_scope { eager_load(:user) } default_scope { eager_load(:user) }
@ -34,4 +36,24 @@ class Expert < ApplicationRecord
@avis_summary = { unanswered: result.unanswered, total: result.total } @avis_summary = { unanswered: result.unanswered, total: result.total }
end end
end end
def merge(old_expert)
procedure_with_new, procedure_without_new = old_expert
.procedures
.partition { |p| p.experts.exists?(id) }
ExpertsProcedure
.where(expert_id: old_expert.id, procedure: procedure_without_new)
.update_all(expert_id: id)
ExpertsProcedure
.where(expert_id: old_expert.id, procedure: procedure_with_new)
.destroy_all
old_expert.commentaires.update_all(expert_id: id)
Avis
.where(claimant_id: old_expert.id, claimant_type: Expert.name)
.update_all(claimant_id: id)
end
end end

View file

@ -10,7 +10,8 @@
# updated_at :datetime # updated_at :datetime
# #
class Instructeur < ApplicationRecord class Instructeur < ApplicationRecord
has_and_belongs_to_many :administrateurs has_many :administrateurs_instructeurs
has_many :administrateurs, through: :administrateurs_instructeurs
has_many :assign_to, dependent: :destroy has_many :assign_to, dependent: :destroy
has_many :groupe_instructeurs, through: :assign_to has_many :groupe_instructeurs, through: :assign_to
@ -19,6 +20,7 @@ class Instructeur < ApplicationRecord
has_many :assign_to_with_email_notifications, -> { with_email_notifications }, class_name: 'AssignTo', inverse_of: :instructeur has_many :assign_to_with_email_notifications, -> { with_email_notifications }, class_name: 'AssignTo', inverse_of: :instructeur
has_many :groupe_instructeur_with_email_notifications, through: :assign_to_with_email_notifications, source: :groupe_instructeur has_many :groupe_instructeur_with_email_notifications, through: :assign_to_with_email_notifications, source: :groupe_instructeur
has_many :commentaires
has_many :dossiers, -> { state_not_brouillon }, through: :groupe_instructeurs has_many :dossiers, -> { state_not_brouillon }, through: :groupe_instructeurs
has_many :follows, -> { active }, inverse_of: :instructeur has_many :follows, -> { active }, inverse_of: :instructeur
has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :instructeur has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :instructeur
@ -249,6 +251,34 @@ class Instructeur < ApplicationRecord
Dossier.connection.select_all(sanitized_query).first Dossier.connection.select_all(sanitized_query).first
end end
def merge(old_instructeur)
old_instructeur
.assign_to
.where.not(groupe_instructeur_id: assign_to.pluck(:groupe_instructeur_id))
.update_all(instructeur_id: id)
old_instructeur
.follows
.where.not(dossier_id: follows.pluck(:dossier_id))
.update_all(instructeur_id: id)
admin_with_new_instructeur, admin_without_new_instructeur = old_instructeur
.administrateurs
.partition { |admin| admin.instructeurs.exists?(id) }
admin_without_new_instructeur.each do |admin|
admin.instructeurs << self
admin.instructeurs.delete(old_instructeur)
end
admin_with_new_instructeur.each do |admin|
admin.instructeurs.delete(old_instructeur)
end
old_instructeur.commentaires.update_all(instructeur_id: id)
old_instructeur.bulk_messages.update_all(instructeur_id: id)
end
private private
def annotations_hash(demande, annotations_privees, avis, messagerie) def annotations_hash(demande, annotations_privees, avis, messagerie)

View file

@ -64,9 +64,12 @@ as well as a link to its edit page.
<dd class="attribute-data attribute-data--<%=attribute.html_class%>"> <dd class="attribute-data attribute-data--<%=attribute.html_class%>">
<%= render_field attribute, page: page %> <%= render_field attribute, page: page %>
<% if attribute.name == 'administrateurs' %> <% if attribute.name == 'administrateurs' %>
<% if procedure.administrateurs.find { |admin| admin.email == current_super_admin.email } %>
<p style="margin-top: 20px;">Vous êtes déjà administrateur sur cette démarche</p>
<% else %>
<%= form_tag(add_administrateur_manager_procedure_path(procedure), style: 'margin-top: 1rem;') do %> <%= form_tag(add_administrateur_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;') %> <button>Devenir administrateur (pour la journée)</button>
<button>Ajouter un administrateur</button> <% end %>
<% end %> <% end %>
<% end %> <% end %>
</dd> </dd>

View file

@ -0,0 +1,5 @@
class AddManagerToAdministrateursProcedures < ActiveRecord::Migration[6.1]
def change
add_column :administrateurs_procedures, :manager, :boolean
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_10_01_143403) do ActiveRecord::Schema.define(version: 2021_10_06_164955) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -76,6 +76,7 @@ ActiveRecord::Schema.define(version: 2021_10_01_143403) do
t.bigint "procedure_id", null: false t.bigint "procedure_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "manager"
t.index ["administrateur_id", "procedure_id"], name: "index_unique_admin_proc_couple", unique: true t.index ["administrateur_id", "procedure_id"], name: "index_unique_admin_proc_couple", unique: true
t.index ["administrateur_id"], name: "index_administrateurs_procedures_on_administrateur_id" t.index ["administrateur_id"], name: "index_administrateurs_procedures_on_administrateur_id"
t.index ["procedure_id"], name: "index_administrateurs_procedures_on_procedure_id" t.index ["procedure_id"], name: "index_administrateurs_procedures_on_procedure_id"

View file

@ -1,4 +1,4 @@
xdescribe Manager::InstructeursController, type: :controller do describe Manager::InstructeursController, type: :controller do
let(:super_admin) { create(:super_admin) } let(:super_admin) { create(:super_admin) }
let(:instructeur) { create(:instructeur) } let(:instructeur) { create(:instructeur) }

View file

@ -1,4 +1,4 @@
xdescribe Manager::ProceduresController, type: :controller do describe Manager::ProceduresController, type: :controller do
let(:super_admin) { create :super_admin } let(:super_admin) { create :super_admin }
before { sign_in super_admin } before { sign_in super_admin }

View file

@ -1,6 +1,8 @@
xdescribe Manager::UsersController, type: :controller do describe Manager::UsersController, type: :controller do
let(:super_admin) { create(:super_admin) } let(:super_admin) { create(:super_admin) }
before { sign_in super_admin }
describe '#show' do describe '#show' do
render_views render_views
@ -8,7 +10,6 @@ xdescribe Manager::UsersController, type: :controller do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
sign_in(super_admin)
get :show, params: { id: user.id } get :show, params: { id: user.id }
end end
@ -16,13 +17,11 @@ xdescribe Manager::UsersController, type: :controller do
end end
describe '#update' do describe '#update' do
let!(:user) { create(:user, email: 'ancien.email@domaine.fr') } let(:user) { create(:user, email: 'ancien.email@domaine.fr') }
before {
sign_in super_admin
}
subject { patch :update, params: { id: user.id, user: { email: nouvel_email } } } subject { patch :update, params: { id: user.id, user: { email: nouvel_email } } }
context 'when the targeted email does not exist' do
describe 'with a valid email' do describe 'with a valid email' do
let(:nouvel_email) { 'nouvel.email@domaine.fr' } let(:nouvel_email) { 'nouvel.email@domaine.fr' }
@ -31,6 +30,17 @@ xdescribe Manager::UsersController, type: :controller do
expect(User.find_by(id: user.id).email).to eq(nouvel_email) expect(User.find_by(id: user.id).email).to eq(nouvel_email)
end end
context 'and the user is an administrateur' do
let(:user) { create(:administrateur).user }
it 'rejects the modification' do
subject
expect(flash[:error]).to match("« nouvel.email@domaine.fr » est un administrateur. On ne sait pas encore faire.")
expect(user.reload.email).not_to eq(nouvel_email)
end
end
end end
describe 'with an invalid email' do describe 'with an invalid email' do
@ -40,15 +50,86 @@ xdescribe Manager::UsersController, type: :controller do
subject subject
expect(User.find_by(id: user.id).email).not_to eq(nouvel_email) expect(User.find_by(id: user.id).email).not_to eq(nouvel_email)
expect(flash[:error]).to match("« #{nouvel_email} » n'est pas une adresse valide.") expect(flash[:error]).to match("Courriel invalide")
end
end
end
context 'when the targeted email exists' do
let(:preexisting_user) { create(:user, email: 'email.existant@domaine.fr') }
let(:nouvel_email) { preexisting_user.email }
context 'and the old account has a dossier' do
let!(:dossier) { create(:dossier, user: user) }
it 'transfers the dossier' do
subject
expect(preexisting_user.dossiers).to match([dossier])
end
end
context 'and the old account belongs to an instructeur and expert' do
let!(:instructeur) { create(:instructeur, user: user) }
let!(:expert) { create(:expert, user: user) }
it 'transfers instructeur account' do
subject
preexisting_user.reload
expect(preexisting_user.instructeur).to match(instructeur)
expect(preexisting_user.expert).to match(expert)
expect(flash[:notice]).to match("Le compte « email.existant@domaine.fr » a absorbé le compte « ancien.email@domaine.fr ».")
end
context 'and the preexisting account owns an instructeur and expert as well' do
let!(:preexisting_instructeur) { create(:instructeur, user: preexisting_user) }
let!(:preexisting_expert) { create(:expert, user: preexisting_user) }
context 'and the source instructeur has some procedures and dossiers' do
let!(:procedure) { create(:procedure, instructeurs: [instructeur]) }
let(:dossier) { create(:dossier) }
let(:administrateur) { create(:administrateur) }
let!(:commentaire) { create(:commentaire, instructeur: instructeur, dossier: dossier) }
let!(:bulk_message) { BulkMessage.create!(instructeur: instructeur, body: 'body', sent_at: Time.zone.now) }
before do
user.instructeur.followed_dossiers << dossier
user.instructeur.administrateurs << administrateur
end
it 'transferts all the stuff' do
subject
preexisting_user.reload
expect(procedure.instructeurs).to match([preexisting_user.instructeur])
expect(preexisting_user.instructeur.followed_dossiers).to match([dossier])
expect(preexisting_user.instructeur.administrateurs).to match([administrateur])
expect(preexisting_user.instructeur.commentaires).to match([commentaire])
expect(preexisting_user.instructeur.bulk_messages).to match([bulk_message])
end
end
context 'and the source expert has some avis and commentaires' do
let(:dossier) { create(:dossier) }
let(:experts_procedure) { create(:experts_procedure, expert: user.expert, procedure: dossier.procedure) }
let!(:avis) { create(:avis, dossier: dossier, claimant: create(:instructeur), experts_procedure: experts_procedure) }
let!(:commentaire) { create(:commentaire, expert: expert, dossier: dossier) }
it 'transfers the avis' do
subject
expect(preexisting_user.expert.avis).to match([avis])
expect(preexisting_user.expert.commentaires).to match([commentaire])
end
end
end
end end
end end
end end
describe '#delete' do describe '#delete' do
let!(:user) { create(:user) } let(:user) { create(:user) }
before { sign_in super_admin }
subject { delete :delete, params: { id: user.id } } subject { delete :delete, params: { id: user.id } }

View file

@ -12,4 +12,67 @@ RSpec.describe Expert, type: :model do
it { expect(ExpertsProcedure.where(expert: expert, procedure: procedure).count).to eq(1) } it { expect(ExpertsProcedure.where(expert: expert, procedure: procedure).count).to eq(1) }
it { expect(ExpertsProcedure.where(expert: expert, procedure: procedure).first.allow_decision_access).to be_falsy } it { expect(ExpertsProcedure.where(expert: expert, procedure: procedure).first.allow_decision_access).to be_falsy }
end end
describe '#merge' do
let(:old_expert) { create(:expert) }
let(:new_expert) { create(:expert) }
subject { new_expert.merge(old_expert) }
context 'when an old expert access a procedure' do
let(:procedure) { create(:procedure) }
before do
procedure.experts << old_expert
subject
end
it 'transfers the access to the new expert' do
expect(procedure.reload.experts). to match_array(new_expert)
end
end
context 'when both expert access a procedure' do
let(:procedure) { create(:procedure) }
before do
procedure.experts << old_expert
procedure.experts << new_expert
subject
end
it 'removes the old one' do
expect(procedure.reload.experts). to match_array(new_expert)
end
end
context 'when an old expert has a commentaire' do
let(:dossier) { create(:dossier) }
let(:commentaire) { CommentaireService.build(old_expert, dossier, body: "Mon commentaire") }
before do
commentaire.save
subject
end
it 'transfers the commentaire to the new expert' do
expect(new_expert.reload.commentaires).to match_array(commentaire)
end
end
context 'when an old expert claims for an avis' do
let!(:avis) { create(:avis, dossier: create(:dossier), claimant: old_expert) }
before do
subject
end
it 'transfers the claim to the new expert' do
avis_claimed_by_new_expert = Avis
.where(claimant_id: new_expert.id, claimant_type: Expert.name)
expect(avis_claimed_by_new_expert).to match_array(avis)
end
end
end
end end

View file

@ -737,6 +737,94 @@ describe Instructeur, type: :model do
end end
end end
describe '#merge' do
let(:old_instructeur) { create(:instructeur) }
let(:new_instructeur) { create(:instructeur) }
subject { new_instructeur.merge(old_instructeur) }
context 'when an procedure is assigned to the old instructeur' do
let(:procedure) { create(:procedure) }
before do
procedure.defaut_groupe_instructeur.instructeurs << old_instructeur
subject
end
it 'transfers the assignment' do
expect(new_instructeur.procedures).to match_array(procedure)
end
end
context 'when both instructeurs are assigned to the same procedure' do
let(:procedure) { create(:procedure) }
before do
procedure.defaut_groupe_instructeur.instructeurs << old_instructeur
procedure.defaut_groupe_instructeur.instructeurs << new_instructeur
subject
end
it 'keeps the assignment' do
expect(new_instructeur.procedures).to match_array(procedure)
end
end
context 'when a dossier is followed by an old instructeur' do
let(:dossier) { create(:dossier) }
before do
old_instructeur.followed_dossiers << dossier
subject
end
it 'transfers the dossier' do
expect(new_instructeur.followed_dossiers).to match_array(dossier)
end
end
context 'when both instructeurs follow the same dossier' do
let(:dossier) { create(:dossier) }
before do
old_instructeur.followed_dossiers << dossier
new_instructeur.followed_dossiers << dossier
subject
end
it 'does not change anything' do
expect(new_instructeur.followed_dossiers.pluck(:id)).to match_array(dossier.id)
end
end
context 'when the old instructeur is on on admin list' do
let(:administrateur) { create(:administrateur) }
before do
administrateur.instructeurs << old_instructeur
subject
end
it 'is replaced by the new one' do
expect(administrateur.reload.instructeurs).to match_array(new_instructeur)
end
end
context 'when both are on the same admin list' do
let(:administrateur) { create(:administrateur) }
before do
administrateur.instructeurs << old_instructeur
administrateur.instructeurs << new_instructeur
subject
end
it 'removes the old one' do
expect(administrateur.reload.instructeurs).to match_array(new_instructeur)
end
end
end
private private
def assign(procedure_to_assign, instructeur_assigne: instructeur) def assign(procedure_to_assign, instructeur_assigne: instructeur)