Merge pull request #4225 from tchak/invite-experts-to-linked-dossier
Invite experts to linked dossiers
This commit is contained in:
commit
9e37074909
14 changed files with 105 additions and 40 deletions
|
@ -10,10 +10,16 @@ module CreateAvisConcern
|
||||||
# the :emails parameter is a 1-element array.
|
# the :emails parameter is a 1-element array.
|
||||||
# Hence the call to first
|
# Hence the call to first
|
||||||
# https://github.com/rails/rails/issues/17225
|
# https://github.com/rails/rails/issues/17225
|
||||||
emails = create_avis_params[:emails].first.split(',').map(&:strip)
|
expert_emails = create_avis_params[:emails].first.split(',').map(&:strip)
|
||||||
|
allowed_dossiers = [dossier]
|
||||||
|
|
||||||
|
if create_avis_params[:invite_linked_dossiers].present?
|
||||||
|
allowed_dossiers += dossier.linked_dossiers
|
||||||
|
end
|
||||||
|
|
||||||
create_results = Avis.create(
|
create_results = Avis.create(
|
||||||
emails.map do |email|
|
expert_emails.flat_map do |email|
|
||||||
|
allowed_dossiers.map do |dossier|
|
||||||
{
|
{
|
||||||
email: email,
|
email: email,
|
||||||
introduction: create_avis_params[:introduction],
|
introduction: create_avis_params[:introduction],
|
||||||
|
@ -22,18 +28,25 @@ module CreateAvisConcern
|
||||||
confidentiel: confidentiel
|
confidentiel: confidentiel
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
end
|
||||||
)
|
)
|
||||||
|
|
||||||
persisted, failed = create_results.partition(&:persisted?)
|
persisted, failed = create_results.partition(&:persisted?)
|
||||||
|
|
||||||
if persisted.any?
|
if persisted.any?
|
||||||
sent_emails_addresses = persisted.map(&:email_to_display).join(", ")
|
sent_emails_addresses = []
|
||||||
flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses}"
|
|
||||||
persisted.each do |avis|
|
persisted.each do |avis|
|
||||||
dossier.demander_un_avis!(avis)
|
avis.dossier.demander_un_avis!(avis)
|
||||||
|
|
||||||
|
if avis.dossier == dossier
|
||||||
|
AvisMailer.avis_invitation(avis).deliver_later
|
||||||
|
sent_emails_addresses << avis.email_to_display
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
flash.notice = "Une demande d'avis a été envoyée à #{sent_emails_addresses.uniq.join(", ")}"
|
||||||
|
end
|
||||||
|
|
||||||
if failed.any?
|
if failed.any?
|
||||||
flash.now.alert = failed
|
flash.now.alert = failed
|
||||||
.filter { |avis| avis.errors.present? }
|
.filter { |avis| avis.errors.present? }
|
||||||
|
@ -41,13 +54,13 @@ module CreateAvisConcern
|
||||||
|
|
||||||
# When an error occurs, return the avis back to the controller
|
# When an error occurs, return the avis back to the controller
|
||||||
# to give the user a chance to correct and resubmit
|
# to give the user a chance to correct and resubmit
|
||||||
Avis.new(create_avis_params.merge(emails: [failed.map(&:email).join(", ")]))
|
Avis.new(create_avis_params.merge(emails: [failed.map(&:email).uniq.join(", ")]))
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_avis_params
|
def create_avis_params
|
||||||
params.require(:avis).permit(:introduction, :confidentiel, emails: [])
|
params.require(:avis).permit(:introduction, :confidentiel, :invite_linked_dossiers, emails: [])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,7 +12,6 @@ class Avis < ApplicationRecord
|
||||||
|
|
||||||
before_validation -> { sanitize_email(:email) }
|
before_validation -> { sanitize_email(:email) }
|
||||||
before_create :try_to_assign_instructeur
|
before_create :try_to_assign_instructeur
|
||||||
after_create :notify_instructeur
|
|
||||||
|
|
||||||
default_scope { joins(:dossier) }
|
default_scope { joins(:dossier) }
|
||||||
scope :with_answer, -> { where.not(answer: nil) }
|
scope :with_answer, -> { where.not(answer: nil) }
|
||||||
|
@ -24,6 +23,7 @@ class Avis < ApplicationRecord
|
||||||
# The form allows subtmitting avis requests to several emails at once,
|
# The form allows subtmitting avis requests to several emails at once,
|
||||||
# hence this virtual attribute.
|
# hence this virtual attribute.
|
||||||
attr_accessor :emails
|
attr_accessor :emails
|
||||||
|
attr_accessor :invite_linked_dossiers
|
||||||
|
|
||||||
def email_to_display
|
def email_to_display
|
||||||
instructeur&.email || email
|
instructeur&.email || email
|
||||||
|
@ -49,10 +49,6 @@ class Avis < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def notify_instructeur
|
|
||||||
AvisMailer.avis_invitation(self).deliver_later
|
|
||||||
end
|
|
||||||
|
|
||||||
def try_to_assign_instructeur
|
def try_to_assign_instructeur
|
||||||
instructeur = Instructeur.find_by(email: email)
|
instructeur = Instructeur.find_by(email: email)
|
||||||
if instructeur
|
if instructeur
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Champ < ApplicationRecord
|
||||||
belongs_to :etablissement, dependent: :destroy
|
belongs_to :etablissement, dependent: :destroy
|
||||||
has_many :champs, -> { ordered }, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
|
has_many :champs, -> { ordered }, foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
|
||||||
|
|
||||||
delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, to: :type_de_champ
|
delegate :libelle, :type_champ, :order_place, :mandatory?, :description, :drop_down_list, :exclude_from_export?, :exclude_from_view?, :repetition?, :dossier_link?, to: :type_de_champ
|
||||||
|
|
||||||
scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) }
|
scope :updated_since?, -> (date) { where('champs.updated_at > ?', date) }
|
||||||
scope :public_only, -> { where(private: false) }
|
scope :public_only, -> { where(private: false) }
|
||||||
|
|
|
@ -517,6 +517,10 @@ class Dossier < ApplicationRecord
|
||||||
self.individual = Individual.create_from_france_connect(fc_information)
|
self.individual = Individual.create_from_france_connect(fc_information)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def linked_dossiers
|
||||||
|
Dossier.where(id: champs.filter(&:dossier_link?).map(&:value).compact)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def log_dossier_operation(author, operation, subject = nil)
|
def log_dossier_operation(author, operation, subject = nil)
|
||||||
|
|
|
@ -157,6 +157,10 @@ class TypeDeChamp < ApplicationRecord
|
||||||
type_champ == TypeDeChamp.type_champs.fetch(:repetition)
|
type_champ == TypeDeChamp.type_champs.fetch(:repetition)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def dossier_link?
|
||||||
|
type_champ == TypeDeChamp.type_champs.fetch(:dossier_link)
|
||||||
|
end
|
||||||
|
|
||||||
def public?
|
def public?
|
||||||
!private?
|
!private?
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
= f.submit 'Envoyer votre avis', class: 'button send'
|
= f.submit 'Envoyer votre avis', class: 'button send'
|
||||||
|
|
||||||
- if !@dossier.termine?
|
- if !@dossier.termine?
|
||||||
= render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis), must_be_confidentiel: @avis.confidentiel?, avis: @new_avis }
|
= render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_avis_path(@avis), linked_dossiers: @dossier.linked_dossiers, must_be_confidentiel: @avis.confidentiel?, avis: @new_avis }
|
||||||
|
|
||||||
- if @dossier.avis_for(current_instructeur).present?
|
- if @dossier.avis_for(current_instructeur).present?
|
||||||
= render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis_for(current_instructeur), avis_seen_at: nil }
|
= render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis_for(current_instructeur), avis_seen_at: nil }
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
.container
|
.container
|
||||||
- if !@dossier.termine?
|
- if !@dossier.termine?
|
||||||
= render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_dossier_path(@dossier.procedure, @dossier), must_be_confidentiel: false, avis: @avis }
|
= render partial: "instructeurs/shared/avis/form", locals: { url: avis_instructeur_dossier_path(@dossier.procedure, @dossier), linked_dossiers: @dossier.linked_dossiers, must_be_confidentiel: false, avis: @avis }
|
||||||
|
|
||||||
- if @dossier.avis.present?
|
- if @dossier.avis.present?
|
||||||
= render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis, avis_seen_at: @avis_seen_at }
|
= render partial: 'instructeurs/shared/avis/list', locals: { avis: @dossier.avis, avis_seen_at: @avis_seen_at }
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
= f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);"
|
= f.email_field :emails, placeholder: 'Adresses email, séparées par des virgules', required: true, multiple: true, onchange: "javascript:DS.replaceSemicolonByComma(event);"
|
||||||
= f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true
|
= f.text_area :introduction, rows: 3, value: avis.introduction || 'Bonjour, merci de me donner votre avis sur ce dossier.', required: true
|
||||||
|
|
||||||
|
- if linked_dossiers.present?
|
||||||
|
= f.check_box :invite_linked_dossiers, value: false
|
||||||
|
= f.label :invite_linked_dossiers, t('helpers.label.invite_linked_dossiers', count: linked_dossiers.length, ids: linked_dossiers.map(&:id).to_sentence)
|
||||||
|
|
||||||
.flex.justify-between.align-baseline
|
.flex.justify-between.align-baseline
|
||||||
- if must_be_confidentiel
|
- if must_be_confidentiel
|
||||||
%p.confidentiel.flex
|
%p.confidentiel.flex
|
||||||
|
|
|
@ -5,3 +5,8 @@ fr:
|
||||||
attributes:
|
attributes:
|
||||||
avis:
|
avis:
|
||||||
answer: "Réponse"
|
answer: "Réponse"
|
||||||
|
helpers:
|
||||||
|
label:
|
||||||
|
invite_linked_dossiers:
|
||||||
|
one: Inviter aussi l'expert sur le dossier lié n° %{ids}
|
||||||
|
other: Inviter aussi l'expert sur les dossiers liés n° %{ids}
|
||||||
|
|
|
@ -123,9 +123,10 @@ describe Instructeurs::AvisController, type: :controller do
|
||||||
let(:intro) { 'introduction' }
|
let(:intro) { 'introduction' }
|
||||||
let(:created_avis) { Avis.last }
|
let(:created_avis) { Avis.last }
|
||||||
let!(:old_avis_count) { Avis.count }
|
let!(:old_avis_count) { Avis.count }
|
||||||
|
let(:invite_linked_dossiers) { nil }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
post :create_avis, params: { id: previous_avis.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel } }
|
post :create_avis, params: { id: previous_avis.id, avis: { emails: emails, introduction: intro, confidentiel: asked_confidentiel, invite_linked_dossiers: invite_linked_dossiers } }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when an invalid email' do
|
context 'when an invalid email' do
|
||||||
|
@ -180,6 +181,34 @@ describe Instructeurs::AvisController, type: :controller do
|
||||||
it { expect(created_avis.confidentiel).to be(true) }
|
it { expect(created_avis.confidentiel).to be(true) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with linked dossiers' do
|
||||||
|
let(:asked_confidentiel) { false }
|
||||||
|
let(:previous_avis_confidentiel) { false }
|
||||||
|
let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com")
|
||||||
|
expect(Avis.count).to eq(old_avis_count + 1)
|
||||||
|
expect(created_avis.email).to eq("a@b.com")
|
||||||
|
expect(created_avis.dossier).to eq(dossier)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'checked' do
|
||||||
|
let(:invite_linked_dossiers) { true }
|
||||||
|
let(:created_avis) { Avis.last(2).first }
|
||||||
|
let(:linked_avis) { Avis.last }
|
||||||
|
let(:linked_dossier) { dossier.reload.linked_dossiers.first }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(flash.notice).to eq("Une demande d'avis a été envoyée à a@b.com")
|
||||||
|
expect(Avis.count).to eq(old_avis_count + 2)
|
||||||
|
expect(created_avis.email).to eq("a@b.com")
|
||||||
|
expect(created_avis.dossier).to eq(dossier)
|
||||||
|
expect(linked_avis.dossier).to eq(linked_dossier)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -54,10 +54,30 @@ FactoryBot.define do
|
||||||
|
|
||||||
trait :with_dossier_link do
|
trait :with_dossier_link do
|
||||||
after(:create) do |dossier, _evaluator|
|
after(:create) do |dossier, _evaluator|
|
||||||
|
# create linked dossier
|
||||||
linked_dossier = create(:dossier)
|
linked_dossier = create(:dossier)
|
||||||
type_de_champ = dossier.procedure.types_de_champ.find { |t| t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link) }
|
|
||||||
champ = dossier.champs.find { |c| c.type_de_champ == type_de_champ }
|
|
||||||
|
|
||||||
|
# find first type de champ dossier_link
|
||||||
|
type_de_champ = dossier.procedure.types_de_champ.find do |t|
|
||||||
|
t.type_champ == TypeDeChamp.type_champs.fetch(:dossier_link)
|
||||||
|
end
|
||||||
|
|
||||||
|
# if type de champ does not exist create it
|
||||||
|
if !type_de_champ
|
||||||
|
type_de_champ = create(:type_de_champ_dossier_link, procedure: dossier.procedure)
|
||||||
|
end
|
||||||
|
|
||||||
|
# find champ with the type de champ
|
||||||
|
champ = dossier.reload.champs.find do |c|
|
||||||
|
c.type_de_champ == type_de_champ
|
||||||
|
end
|
||||||
|
|
||||||
|
# if champ does not exist create it
|
||||||
|
if !champ
|
||||||
|
champ = create(:champ_dossier_link, dossier: dossier, type_de_champ: type_de_champ)
|
||||||
|
end
|
||||||
|
|
||||||
|
# set champ value with linked dossier
|
||||||
champ.value = linked_dossier.id
|
champ.value = linked_dossier.id
|
||||||
champ.save!
|
champ.save!
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ feature 'Inviting an expert:' do
|
||||||
let(:expert) { create(:instructeur, password: expert_password) }
|
let(:expert) { create(:instructeur, password: expert_password) }
|
||||||
let(:expert_password) { 'mot de passe d’expert' }
|
let(:expert_password) { 'mot de passe d’expert' }
|
||||||
let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
|
let(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
|
||||||
let(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) }
|
let(:dossier) { create(:dossier, :en_construction, :with_dossier_link, procedure: procedure) }
|
||||||
|
|
||||||
context 'as an Instructeur' do
|
context 'as an Instructeur' do
|
||||||
scenario 'I can invite an expert' do
|
scenario 'I can invite an expert' do
|
||||||
|
@ -20,6 +20,7 @@ feature 'Inviting an expert:' do
|
||||||
|
|
||||||
fill_in 'avis_emails', with: 'expert1@exemple.fr, expert2@exemple.fr'
|
fill_in 'avis_emails', with: 'expert1@exemple.fr, expert2@exemple.fr'
|
||||||
fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.'
|
fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.'
|
||||||
|
check 'avis_invite_linked_dossiers'
|
||||||
page.select 'confidentiel', from: 'avis_confidentiel'
|
page.select 'confidentiel', from: 'avis_confidentiel'
|
||||||
|
|
||||||
perform_enqueued_jobs do
|
perform_enqueued_jobs do
|
||||||
|
@ -34,6 +35,8 @@ feature 'Inviting an expert:' do
|
||||||
expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.')
|
expect(page).to have_content('Bonjour, merci de me donner votre avis sur ce dossier.')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
expect(Avis.count).to eq(4)
|
||||||
|
expect(all_emails.size).to eq(2)
|
||||||
invitation_email = open_email('expert2@exemple.fr')
|
invitation_email = open_email('expert2@exemple.fr')
|
||||||
avis = Avis.find_by(email: 'expert2@exemple.fr')
|
avis = Avis.find_by(email: 'expert2@exemple.fr')
|
||||||
sign_up_link = sign_up_instructeur_avis_path(avis.id, avis.email)
|
sign_up_link = sign_up_instructeur_avis_path(avis.id, avis.email)
|
||||||
|
|
|
@ -7,8 +7,7 @@ feature 'Instructing a dossier:' do
|
||||||
let!(:instructeur) { create(:instructeur, password: password) }
|
let!(:instructeur) { create(:instructeur, password: password) }
|
||||||
|
|
||||||
let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
|
let!(:procedure) { create(:procedure, :published, instructeurs: [instructeur]) }
|
||||||
let!(:dossier) { create(:dossier, state: Dossier.states.fetch(:en_construction), procedure: procedure) }
|
let!(:dossier) { create(:dossier, :en_construction, procedure: procedure) }
|
||||||
|
|
||||||
context 'the instructeur is also a user' do
|
context 'the instructeur is also a user' do
|
||||||
scenario 'a instructeur can fill a dossier' do
|
scenario 'a instructeur can fill a dossier' do
|
||||||
visit commencer_path(path: procedure.path)
|
visit commencer_path(path: procedure.path)
|
||||||
|
|
|
@ -87,18 +87,6 @@ RSpec.describe Avis, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#notify_instructeur' do
|
|
||||||
context 'when an avis is created' do
|
|
||||||
before do
|
|
||||||
avis_invitation_double = double('avis_invitation', deliver_later: true)
|
|
||||||
allow(AvisMailer).to receive(:avis_invitation).and_return(avis_invitation_double)
|
|
||||||
Avis.create(claimant: claimant, email: 'email@l.com')
|
|
||||||
end
|
|
||||||
|
|
||||||
it { expect(AvisMailer).to have_received(:avis_invitation) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#try_to_assign_instructeur' do
|
describe '#try_to_assign_instructeur' do
|
||||||
let!(:instructeur) { create(:instructeur) }
|
let!(:instructeur) { create(:instructeur) }
|
||||||
let(:avis) { Avis.create(claimant: claimant, email: email, dossier: create(:dossier)) }
|
let(:avis) { Avis.create(claimant: claimant, email: email, dossier: create(:dossier)) }
|
||||||
|
|
Loading…
Reference in a new issue