Merge pull request #5846 from betagouv/expert
Création du modèle expert
This commit is contained in:
commit
a7b40c4102
17 changed files with 249 additions and 13 deletions
|
@ -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
|
||||
|
|
|
@ -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
11
app/models/expert.rb
Normal 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
|
17
app/models/experts_procedure.rb
Normal file
17
app/models/experts_procedure.rb
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
7
db/migrate/20210107143316_create_experts.rb
Normal file
7
db/migrate/20210107143316_create_experts.rb
Normal 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
|
6
db/migrate/20210107143938_link_user_and_expert.rb
Normal file
6
db/migrate/20210107143938_link_user_and_expert.rb
Normal 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
|
11
db/migrate/20210112120658_create_experts_procedures.rb
Normal file
11
db/migrate/20210112120658_create_experts_procedures.rb
Normal 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
|
|
@ -0,0 +1,5 @@
|
|||
class AddExpertsProcedureToAvis < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_reference :avis, :experts_procedure, foreign_key: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddUniqueIndexToExpertsProcedures < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
add_index :experts_procedures, [:expert_id, :procedure_id], unique: true
|
||||
end
|
||||
end
|
26
db/schema.rb
26
db/schema.rb
|
@ -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
|
||||
|
|
|
@ -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
14
spec/factories/expert.rb
Normal 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
|
|
@ -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' }
|
||||
|
|
15
spec/models/expert_spec.rb
Normal file
15
spec/models/expert_spec.rb
Normal 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
|
|
@ -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) }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue