Merge pull request #5846 from betagouv/expert

Création du modèle expert
This commit is contained in:
Kara Diaby 2021-01-22 15:55:07 +01:00 committed by GitHub
commit a7b40c4102
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 249 additions and 13 deletions

View file

@ -19,6 +19,8 @@ module CreateAvisConcern
create_results = Avis.create(
expert_emails.flat_map do |email|
expert = User.create_or_promote_to_expert(email, SecureRandom.hex).expert
experts_procedure = ExpertsProcedure.find_or_create_by(procedure: dossier.procedure, expert: expert)
allowed_dossiers.map do |dossier|
{
email: email,
@ -26,7 +28,8 @@ module CreateAvisConcern
introduction_file: create_avis_params[:introduction_file],
claimant: current_instructeur,
dossier: dossier,
confidentiel: confidentiel
confidentiel: confidentiel,
experts_procedure: experts_procedure
}
end
end

View file

@ -2,27 +2,30 @@
#
# Table name: avis
#
# id :integer not null, primary key
# answer :text
# confidentiel :boolean default(FALSE), not null
# email :string
# introduction :text
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# claimant_id :integer not null
# dossier_id :integer
# instructeur_id :integer
# id :integer not null, primary key
# answer :text
# confidentiel :boolean default(FALSE), not null
# email :string
# introduction :text
# revoked_at :datetime
# created_at :datetime not null
# updated_at :datetime not null
# claimant_id :integer not null
# dossier_id :integer
# experts_procedure_id :bigint
# instructeur_id :integer
#
class Avis < ApplicationRecord
include EmailSanitizableConcern
belongs_to :dossier, inverse_of: :avis, touch: true, optional: false
belongs_to :instructeur, optional: true
belongs_to :experts_procedure, optional: true
belongs_to :claimant, class_name: 'Instructeur', optional: false
has_one_attached :piece_justificative_file
has_one_attached :introduction_file
has_one :expert, through: :experts_procedure
validates :piece_justificative_file,
content_type: AUTHORIZED_CONTENT_TYPES,

11
app/models/expert.rb Normal file
View file

@ -0,0 +1,11 @@
# == Schema Information
#
# Table name: experts
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
#
class Expert < ApplicationRecord
has_one :user
end

View file

@ -0,0 +1,17 @@
# == Schema Information
#
# Table name: experts_procedures
#
# id :bigint not null, primary key
# allow_decision_access :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
# expert_id :bigint not null
# procedure_id :bigint not null
#
class ExpertsProcedure < ApplicationRecord
belongs_to :expert
belongs_to :procedure
has_many :avis, dependent: :destroy
end

View file

@ -70,6 +70,9 @@ class Procedure < ApplicationRecord
has_many :all_types_de_champ, -> { joins(:procedure).where('types_de_champ.revision_id != procedures.draft_revision_id').ordered }, through: :revisions, source: :types_de_champ
has_many :all_types_de_champ_private, -> { joins(:procedure).where('types_de_champ.revision_id != procedures.draft_revision_id').ordered }, through: :revisions, source: :types_de_champ_private
has_many :experts_procedures, dependent: :destroy
has_many :experts, through: :experts_procedures
has_one :module_api_carto, dependent: :destroy
has_one :attestation_template, dependent: :destroy

View file

@ -25,6 +25,7 @@
# created_at :datetime
# updated_at :datetime
# administrateur_id :bigint
# expert_id :bigint
# instructeur_id :bigint
#
class User < ApplicationRecord
@ -48,6 +49,7 @@ class User < ApplicationRecord
has_one :france_connect_information, dependent: :destroy
belongs_to :instructeur, optional: true
belongs_to :administrateur, optional: true
belongs_to :expert, optional: true
accepts_nested_attributes_for :france_connect_information
@ -128,6 +130,20 @@ class User < ApplicationRecord
user
end
def self.create_or_promote_to_expert(email, password)
user = User
.create_with(password: password, confirmed_at: Time.zone.now)
.find_or_create_by(email: email)
if user.valid?
if user.expert_id.nil?
user.create_expert!
end
end
user
end
def flipper_id
"User:#{id}"
end

View file

@ -0,0 +1,7 @@
class CreateExperts < ActiveRecord::Migration[6.0]
def change
create_table :experts do |t| # rubocop:disable Style/SymbolProc
t.timestamps
end
end
end

View file

@ -0,0 +1,6 @@
class LinkUserAndExpert < ActiveRecord::Migration[6.0]
def change
add_reference :users, :expert, index: true
add_foreign_key :users, :experts
end
end

View file

@ -0,0 +1,11 @@
class CreateExpertsProcedures < ActiveRecord::Migration[6.0]
def change
create_table :experts_procedures do |t|
t.references :expert, null: false, foreign_key: true
t.references :procedure, null: false, foreign_key: true
t.boolean :allow_decision_access, default: false, null: false
t.timestamps
end
end
end

View file

@ -0,0 +1,5 @@
class AddExpertsProcedureToAvis < ActiveRecord::Migration[6.0]
def change
add_reference :avis, :experts_procedure, foreign_key: true
end
end

View file

@ -0,0 +1,5 @@
class AddUniqueIndexToExpertsProcedures < ActiveRecord::Migration[6.0]
def change
add_index :experts_procedures, [:expert_id, :procedure_id], unique: true
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_01_14_224721) do
ActiveRecord::Schema.define(version: 2021_01_21_134435) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -121,8 +121,10 @@ ActiveRecord::Schema.define(version: 2021_01_14_224721) do
t.integer "claimant_id", null: false
t.boolean "confidentiel", default: false, null: false
t.datetime "revoked_at"
t.bigint "experts_procedure_id"
t.index ["claimant_id"], name: "index_avis_on_claimant_id"
t.index ["dossier_id"], name: "index_avis_on_dossier_id"
t.index ["experts_procedure_id"], name: "index_avis_on_experts_procedure_id"
t.index ["instructeur_id"], name: "index_avis_on_instructeur_id"
end
@ -321,6 +323,22 @@ ActiveRecord::Schema.define(version: 2021_01_14_224721) do
t.datetime "updated_at"
end
create_table "experts", force: :cascade do |t|
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "experts_procedures", force: :cascade do |t|
t.bigint "expert_id", null: false
t.bigint "procedure_id", null: false
t.boolean "allow_decision_access", default: false, null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["expert_id", "procedure_id"], name: "index_experts_procedures_on_expert_id_and_procedure_id", unique: true
t.index ["expert_id"], name: "index_experts_procedures_on_expert_id"
t.index ["procedure_id"], name: "index_experts_procedures_on_procedure_id"
end
create_table "exports", force: :cascade do |t|
t.string "format", null: false
t.datetime "created_at", null: false
@ -676,9 +694,11 @@ ActiveRecord::Schema.define(version: 2021_01_14_224721) do
t.datetime "locked_at"
t.bigint "instructeur_id"
t.bigint "administrateur_id"
t.bigint "expert_id"
t.index ["administrateur_id"], name: "index_users_on_administrateur_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["expert_id"], name: "index_users_on_expert_id"
t.index ["instructeur_id"], name: "index_users_on_instructeur_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
@ -706,6 +726,7 @@ ActiveRecord::Schema.define(version: 2021_01_14_224721) do
add_foreign_key "assign_tos", "groupe_instructeurs"
add_foreign_key "attestation_templates", "procedures"
add_foreign_key "attestations", "dossiers"
add_foreign_key "avis", "experts_procedures"
add_foreign_key "avis", "instructeurs", column: "claimant_id"
add_foreign_key "champs", "champs", column: "parent_id"
add_foreign_key "closed_mails", "procedures"
@ -715,6 +736,8 @@ ActiveRecord::Schema.define(version: 2021_01_14_224721) do
add_foreign_key "dossiers", "groupe_instructeurs"
add_foreign_key "dossiers", "procedure_revisions", column: "revision_id"
add_foreign_key "dossiers", "users"
add_foreign_key "experts_procedures", "experts"
add_foreign_key "experts_procedures", "procedures"
add_foreign_key "feedbacks", "users"
add_foreign_key "geo_areas", "champs"
add_foreign_key "groupe_instructeurs", "procedures"
@ -734,6 +757,7 @@ ActiveRecord::Schema.define(version: 2021_01_14_224721) do
add_foreign_key "types_de_champ", "procedure_revisions", column: "revision_id"
add_foreign_key "types_de_champ", "types_de_champ", column: "parent_id"
add_foreign_key "users", "administrateurs"
add_foreign_key "users", "experts"
add_foreign_key "users", "instructeurs"
add_foreign_key "without_continuation_mails", "procedures"
end

View file

@ -0,0 +1,28 @@
namespace :after_party do
desc 'Deployment task: backfill_expert_id_on_avis_table'
task backfill_experts_procedure_id_on_avis_table: :environment do
puts "Running deploy task 'backfill_expert_id_on_avis_table'"
# rubocop:disable DS/Unscoped
# rubocop:disable Rails/PluckInWhere
Instructeur.includes(:user)
.where(id: Avis.unscoped.pluck(:instructeur_id))
.where.not(users: { instructeur_id: nil })
.find_each do |instructeur|
user = instructeur.user
User.create_or_promote_to_expert(user.email, SecureRandom.hex)
user.reload
# rubocop:enable DS/Unscoped
# rubocop:enable Rails/PluckInWhere
instructeur.avis.each do |avis|
experts_procedure = ExpertsProcedure.find_or_create_by(expert: user.expert, procedure: avis.procedure)
avis.update_column(:experts_procedure_id, experts_procedure.id)
end
end
# Update task as completed. If you remove the line below, the task will
# run with every deploy (or every time you call after_party:run).
AfterParty::TaskRecord
.create version: AfterParty::TaskRecorder.new(__FILE__).timestamp
end
end

14
spec/factories/expert.rb Normal file
View file

@ -0,0 +1,14 @@
FactoryBot.define do
sequence(:create_expert_email) { |n| "expert#{n}@expert.com" }
factory :expert do
transient do
email { generate(:expert_email) }
password { 'somethingverycomplated!' }
end
initialize_with do
User.create_or_promote_to_expert(email, password).expert
end
end
end

View file

@ -56,6 +56,23 @@ RSpec.describe Avis, type: :model do
end
end
describe "an avis is linked to an expert_procedure" do
let(:procedure) { create(:procedure) }
let(:expert) { create(:expert) }
let(:experts_procedure) { ExpertsProcedure.create(procedure: procedure, expert: expert) }
context 'an avis is linked to an experts_procedure' do
let!(:avis) { create(:avis, email: nil, experts_procedure: experts_procedure) }
before do
avis.reload
end
it { expect(avis.email).to be_nil }
it { expect(avis.experts_procedure).to eq(experts_procedure) }
end
end
describe '.avis_exists_and_email_belongs_to_avis?' do
let(:dossier) { create(:dossier) }
let(:invited_email) { 'invited@avis.com' }

View file

@ -0,0 +1,15 @@
RSpec.describe Expert, type: :model do
describe 'an expert could be add to a procedure' do
let(:procedure) { create(:procedure) }
let(:expert) { create(:expert) }
before do
procedure.experts << expert
procedure.reload
end
it { expect(procedure.experts).to eq([expert]) }
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 }
end
end

View file

@ -163,6 +163,57 @@ describe User, type: :model do
end
end
describe '.create_or_promote_to_expert' do
let(:email) { 'exp1@gmail.com' }
let(:password) { 'un super expert !' }
subject { User.create_or_promote_to_expert(email, password) }
context 'with an invalid email' do
let(:email) { 'invalid' }
it 'does not build an expert' do
user = subject
expect(user.valid?).to be false
expect(user.expert).to be_nil
end
end
context 'without an existing user' do
it do
user = subject
expect(user.valid_password?(password)).to be true
expect(user.confirmed_at).to be_present
expect(user.expert).to be_present
end
end
context 'with an existing user' do
before { create(:user, email: email, password: 'my-s3cure-p4ssword') }
it 'keeps the previous password' do
user = subject
expect(user.valid_password?('my-s3cure-p4ssword')).to be true
expect(user.expert).to be_present
end
context 'with an existing expert' do
let!(:expert) { Expert.create }
before do
User
.find_by(email: email)
.update!(expert: expert)
end
it 'keeps the existing experts' do
user = subject
expect(user.expert).to eq(expert)
end
end
end
end
describe 'invite_administrateur!' do
let(:super_admin) { create(:super_admin) }
let(:administrateur) { create(:administrateur) }