commit
6dadc2422f
43 changed files with 812 additions and 137 deletions
2
Gemfile
2
Gemfile
|
@ -4,7 +4,7 @@ gem 'aasm'
|
||||||
gem 'actiontext', git: 'https://github.com/kobaltz/actiontext.git', branch: 'archive', require: 'action_text' # Port of ActionText to Rails 5
|
gem 'actiontext', git: 'https://github.com/kobaltz/actiontext.git', branch: 'archive', require: 'action_text' # Port of ActionText to Rails 5
|
||||||
gem 'active_link_to' # Automatically set a class on active links
|
gem 'active_link_to' # Automatically set a class on active links
|
||||||
gem 'active_model_serializers'
|
gem 'active_model_serializers'
|
||||||
gem 'activestorage-openstack', git: 'https://github.com/fredZen/activestorage-openstack.git', branch: 'frederic/fix_upload_signature'
|
gem 'activestorage-openstack'
|
||||||
gem 'administrate'
|
gem 'administrate'
|
||||||
gem 'after_party'
|
gem 'after_party'
|
||||||
gem 'anchored'
|
gem 'anchored'
|
||||||
|
|
34
Gemfile.lock
34
Gemfile.lock
|
@ -1,14 +1,3 @@
|
||||||
GIT
|
|
||||||
remote: https://github.com/fredZen/activestorage-openstack.git
|
|
||||||
revision: c71d5107a51701eab9d9267dd0000e6c1cf3e39a
|
|
||||||
branch: frederic/fix_upload_signature
|
|
||||||
specs:
|
|
||||||
activestorage-openstack (0.5.0)
|
|
||||||
fog-openstack (~> 1.0)
|
|
||||||
marcel
|
|
||||||
mime-types
|
|
||||||
rails (~> 5.2.0)
|
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
remote: https://github.com/kobaltz/actiontext.git
|
remote: https://github.com/kobaltz/actiontext.git
|
||||||
revision: ef59c4ba99d1b7614dd47f5a294eef553224db88
|
revision: ef59c4ba99d1b7614dd47f5a294eef553224db88
|
||||||
|
@ -75,13 +64,18 @@ GEM
|
||||||
actionpack (= 5.2.2.1)
|
actionpack (= 5.2.2.1)
|
||||||
activerecord (= 5.2.2.1)
|
activerecord (= 5.2.2.1)
|
||||||
marcel (~> 0.3.1)
|
marcel (~> 0.3.1)
|
||||||
|
activestorage-openstack (1.0.0)
|
||||||
|
fog-openstack (~> 1.0)
|
||||||
|
marcel
|
||||||
|
mime-types
|
||||||
|
rails (<= 6)
|
||||||
activesupport (5.2.2.1)
|
activesupport (5.2.2.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.2)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
administrate (0.11.0)
|
administrate (0.11.0)
|
||||||
actionpack (>= 4.2, < 6.0)
|
actionpack (>= 4.2, < 6.0)
|
||||||
actionview (>= 4.2, < 6.0)
|
actionview (>= 4.2, < 6.0)
|
||||||
|
@ -125,18 +119,18 @@ GEM
|
||||||
browser (2.5.3)
|
browser (2.5.3)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
byebug (10.0.2)
|
byebug (10.0.2)
|
||||||
capybara (3.12.0)
|
capybara (3.29.0)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
rack (>= 1.6.0)
|
rack (>= 1.6.0)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (~> 1.2)
|
regexp_parser (~> 1.5)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
capybara-email (3.0.1)
|
capybara-email (3.0.1)
|
||||||
capybara (>= 2.4, < 4.0)
|
capybara (>= 2.4, < 4.0)
|
||||||
mail
|
mail
|
||||||
capybara-screenshot (1.0.22)
|
capybara-screenshot (1.0.23)
|
||||||
capybara (>= 1.0, < 4)
|
capybara (>= 1.0, < 4)
|
||||||
launchy
|
launchy
|
||||||
capybara-selenium (0.0.6)
|
capybara-selenium (0.0.6)
|
||||||
|
@ -379,7 +373,7 @@ GEM
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2018.0812)
|
mime-types-data (3.2018.0812)
|
||||||
mimemagic (0.3.3)
|
mimemagic (0.3.3)
|
||||||
mini_mime (1.0.1)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.11.3)
|
minitest (5.11.3)
|
||||||
momentjs-rails (2.20.1)
|
momentjs-rails (2.20.1)
|
||||||
|
@ -454,7 +448,7 @@ GEM
|
||||||
pry-byebug (3.6.0)
|
pry-byebug (3.6.0)
|
||||||
byebug (~> 10.0)
|
byebug (~> 10.0)
|
||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
public_suffix (3.0.3)
|
public_suffix (4.0.1)
|
||||||
puma (3.12.0)
|
puma (3.12.0)
|
||||||
pundit (2.0.1)
|
pundit (2.0.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -518,7 +512,7 @@ GEM
|
||||||
execjs
|
execjs
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
tilt
|
tilt
|
||||||
regexp_parser (1.3.0)
|
regexp_parser (1.6.0)
|
||||||
request_store (1.4.1)
|
request_store (1.4.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.0.0)
|
responders (3.0.0)
|
||||||
|
@ -717,7 +711,7 @@ DEPENDENCIES
|
||||||
actiontext!
|
actiontext!
|
||||||
active_link_to
|
active_link_to
|
||||||
active_model_serializers
|
active_model_serializers
|
||||||
activestorage-openstack!
|
activestorage-openstack
|
||||||
administrate
|
administrate
|
||||||
after_party
|
after_party
|
||||||
anchored
|
anchored
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
.groupe-instructeur {
|
||||||
|
.actions {
|
||||||
|
width: 200px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
@import "constants";
|
||||||
|
|
||||||
.pull-left {
|
.pull-left {
|
||||||
float: left;
|
float: left;
|
||||||
|
@ -48,3 +49,7 @@
|
||||||
background: $orange-bg;
|
background: $orange-bg;
|
||||||
color: $black;
|
color: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: 2 * $default-spacer;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -264,7 +264,7 @@ module Instructeurs
|
||||||
end
|
end
|
||||||
|
|
||||||
def ensure_ownership!
|
def ensure_ownership!
|
||||||
if !procedure.defaut_groupe_instructeur.instructeurs.include?(current_instructeur)
|
if !current_instructeur.procedures.include?(procedure)
|
||||||
flash[:alert] = "Vous n'avez pas accès à cette démarche"
|
flash[:alert] = "Vous n'avez pas accès à cette démarche"
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
module NewAdministrateur
|
||||||
|
class GroupeInstructeursController < AdministrateurController
|
||||||
|
ITEMS_PER_PAGE = 25
|
||||||
|
|
||||||
|
def index
|
||||||
|
@procedure = procedure
|
||||||
|
|
||||||
|
@groupes_instructeurs = paginated_groupe_instructeurs
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@procedure = procedure
|
||||||
|
@groupe_instructeur = groupe_instructeur
|
||||||
|
@instructeurs = paginated_instructeurs
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@groupe_instructeur = procedure
|
||||||
|
.groupe_instructeurs
|
||||||
|
.new(label: label, instructeurs: [current_administrateur.instructeur])
|
||||||
|
|
||||||
|
if @groupe_instructeur.save
|
||||||
|
redirect_to procedure_groupe_instructeur_path(procedure, @groupe_instructeur),
|
||||||
|
notice: "Le groupe d’instructeurs « #{label} » a été créé."
|
||||||
|
else
|
||||||
|
@procedure = procedure
|
||||||
|
@groupes_instructeurs = paginated_groupe_instructeurs
|
||||||
|
|
||||||
|
flash[:alert] = "le nom « #{label} » est déjà pris par un autre groupe."
|
||||||
|
render :index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@groupe_instructeur = groupe_instructeur
|
||||||
|
|
||||||
|
if @groupe_instructeur.update(label: label)
|
||||||
|
redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur),
|
||||||
|
notice: "Le nom est à présent « #{label} »."
|
||||||
|
else
|
||||||
|
@procedure = procedure
|
||||||
|
@instructeurs = paginated_instructeurs
|
||||||
|
|
||||||
|
flash[:alert] = "le nom « #{label} » est déjà pris par un autre groupe."
|
||||||
|
render :show
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_instructeur
|
||||||
|
@instructeur = Instructeur.find_by(email: instructeur_email) ||
|
||||||
|
create_instructeur(instructeur_email)
|
||||||
|
|
||||||
|
if groupe_instructeur.instructeurs.include?(@instructeur)
|
||||||
|
flash[:alert] = "L’instructeur « #{instructeur_email} » est déjà dans le groupe."
|
||||||
|
|
||||||
|
else
|
||||||
|
groupe_instructeur.instructeurs << @instructeur
|
||||||
|
flash[:notice] = "L’instructeur « #{instructeur_email} » a été affecté au groupe."
|
||||||
|
GroupeInstructeurMailer
|
||||||
|
.add_instructeur(groupe_instructeur, @instructeur, current_user.email)
|
||||||
|
.deliver_later
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_instructeur
|
||||||
|
if groupe_instructeur.instructeurs.one?
|
||||||
|
flash[:alert] = "Suppression impossible : il doit y avoir au moins un instructeur dans le groupe"
|
||||||
|
|
||||||
|
else
|
||||||
|
@instructeur = Instructeur.find(instructeur_id)
|
||||||
|
groupe_instructeur.instructeurs.destroy(@instructeur)
|
||||||
|
flash[:notice] = "L’instructeur « #{@instructeur.email} » a été retiré du groupe."
|
||||||
|
GroupeInstructeurMailer
|
||||||
|
.remove_instructeur(groupe_instructeur, @instructeur, current_user.email)
|
||||||
|
.deliver_later
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to procedure_groupe_instructeur_path(procedure, groupe_instructeur)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_routing_criteria_name
|
||||||
|
procedure.update!(routing_criteria_name: routing_criteria_name)
|
||||||
|
|
||||||
|
redirect_to procedure_groupe_instructeurs_path(procedure),
|
||||||
|
notice: "Le libellé est maintenant « #{procedure.routing_criteria_name} »."
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_instructeur(email)
|
||||||
|
user = User.create_or_promote_to_instructeur(
|
||||||
|
email,
|
||||||
|
SecureRandom.hex,
|
||||||
|
administrateurs: [current_administrateur]
|
||||||
|
)
|
||||||
|
user.invite!
|
||||||
|
user.instructeur
|
||||||
|
end
|
||||||
|
|
||||||
|
def procedure
|
||||||
|
current_administrateur
|
||||||
|
.procedures
|
||||||
|
.includes(:groupe_instructeurs)
|
||||||
|
.find(params[:procedure_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def groupe_instructeur
|
||||||
|
procedure.groupe_instructeurs.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def instructeur_email
|
||||||
|
params[:instructeur][:email].strip.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
def instructeur_id
|
||||||
|
params[:instructeur][:id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def label
|
||||||
|
params[:groupe_instructeur][:label]
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_groupe_instructeurs
|
||||||
|
procedure
|
||||||
|
.groupe_instructeurs
|
||||||
|
.page(params[:page])
|
||||||
|
.per(ITEMS_PER_PAGE)
|
||||||
|
.order(:label)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_instructeurs
|
||||||
|
groupe_instructeur
|
||||||
|
.instructeurs
|
||||||
|
.page(params[:page])
|
||||||
|
.per(ITEMS_PER_PAGE)
|
||||||
|
.order(:email)
|
||||||
|
end
|
||||||
|
|
||||||
|
def routing_criteria_name
|
||||||
|
params[:procedure][:routing_criteria_name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -538,7 +538,7 @@ enum TypeDeChamp {
|
||||||
multiple_drop_down_list
|
multiple_drop_down_list
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Nombre entier
|
Nombre
|
||||||
"""
|
"""
|
||||||
number
|
number
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module DossierLinkHelper
|
module DossierLinkHelper
|
||||||
def dossier_linked_path(user, dossier)
|
def dossier_linked_path(user, dossier)
|
||||||
if user.is_a?(Instructeur)
|
if user.is_a?(Instructeur)
|
||||||
if dossier.procedure.defaut_groupe_instructeur.instructeurs.include?(user)
|
if user.groupe_instructeurs.include?(dossier.groupe_instructeur)
|
||||||
instructeur_dossier_path(dossier.procedure, dossier)
|
instructeur_dossier_path(dossier.procedure, dossier)
|
||||||
else
|
else
|
||||||
avis = dossier.avis.find_by(instructeur: user)
|
avis = dossier.avis.find_by(instructeur: user)
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
module ActiveStorage
|
|
||||||
# Wraps an ActiveStorage::Service to route direct upload and direct download URLs through our proxy,
|
|
||||||
# thus avoiding exposing the storage provider’s URL to our end-users.
|
|
||||||
class Service::DsProxyService < SimpleDelegator
|
|
||||||
attr_reader :wrapped
|
|
||||||
|
|
||||||
def self.build(wrapped:, configurator:, **options)
|
|
||||||
new(wrapped: configurator.build(wrapped))
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(wrapped:)
|
|
||||||
@wrapped = wrapped
|
|
||||||
super(wrapped)
|
|
||||||
end
|
|
||||||
|
|
||||||
def url(*args)
|
|
||||||
url = wrapped.url(*args)
|
|
||||||
publicize(url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def url_for_direct_upload(*args)
|
|
||||||
url = wrapped.url_for_direct_upload(*args)
|
|
||||||
publicize(url)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def object_for(key, &block)
|
|
||||||
blob_url = url(key)
|
|
||||||
if block_given?
|
|
||||||
request = Typhoeus::Request.new(blob_url)
|
|
||||||
request.on_headers do |response|
|
|
||||||
if response.code != 200
|
|
||||||
raise Fog::OpenStack::Storage::NotFound.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
request.on_body do |chunk|
|
|
||||||
yield chunk
|
|
||||||
end
|
|
||||||
request.run
|
|
||||||
else
|
|
||||||
response = Typhoeus.get(blob_url)
|
|
||||||
if response.success?
|
|
||||||
response
|
|
||||||
else
|
|
||||||
raise Fog::OpenStack::Storage::NotFound.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def publicize(url)
|
|
||||||
search = %r{^https://[^/]+/v1/AUTH_[a-f0-9]{32}}
|
|
||||||
replace = 'https://static.demarches-simplifiees.fr'
|
|
||||||
url.gsub(search, replace)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
25
app/mailers/groupe_instructeur_mailer.rb
Normal file
25
app/mailers/groupe_instructeur_mailer.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
class GroupeInstructeurMailer < ApplicationMailer
|
||||||
|
layout 'mailers/layout'
|
||||||
|
|
||||||
|
def add_instructeur(group, instructeur, current_instructeur_email)
|
||||||
|
@email = instructeur.email
|
||||||
|
@group = group
|
||||||
|
@current_instructeur_email = current_instructeur_email
|
||||||
|
|
||||||
|
subject = "Ajout d’un instructeur dans le groupe \"#{group.label}\""
|
||||||
|
|
||||||
|
emails = @group.instructeurs.pluck(:email)
|
||||||
|
mail(bcc: emails, subject: subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_instructeur(group, instructeur, current_instructeur_email)
|
||||||
|
@email = instructeur.email
|
||||||
|
@group = group
|
||||||
|
@current_instructeur_email = current_instructeur_email
|
||||||
|
|
||||||
|
subject = "Suppression d’un instructeur dans le groupe \"#{group.label}\""
|
||||||
|
|
||||||
|
emails = @group.instructeurs.pluck(:email)
|
||||||
|
mail(bcc: emails, subject: subject)
|
||||||
|
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)
|
||||||
|
|
|
@ -4,4 +4,9 @@ class GroupeInstructeur < ApplicationRecord
|
||||||
has_many :assign_tos
|
has_many :assign_tos
|
||||||
has_many :instructeurs, through: :assign_tos, dependent: :destroy
|
has_many :instructeurs, through: :assign_tos, dependent: :destroy
|
||||||
has_many :dossiers
|
has_many :dossiers
|
||||||
|
|
||||||
|
validates :label, presence: { message: 'doit être renseigné' }, allow_nil: false
|
||||||
|
validates :label, uniqueness: { scope: :procedure, message: 'existe déjà' }
|
||||||
|
|
||||||
|
before_validation -> { label&.strip! }
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
%p
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
%p
|
||||||
|
L’instructeur « #{@email} » a été affecté au groupe « #{@group.label} » par « #{@current_instructeur_email} », en charge de la démarche « #{@group.procedure.libelle} ».
|
||||||
|
|
||||||
|
%p
|
||||||
|
Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe :
|
||||||
|
= link_to(@group.label, procedure_groupe_instructeur_url(@group.procedure, @group))
|
||||||
|
|
||||||
|
= render partial: "layouts/mailers/signature"
|
|
@ -0,0 +1,11 @@
|
||||||
|
%p
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
%p
|
||||||
|
L’instructeur « #{@email} » a été retiré du groupe « #{@group.label} » par « #{@current_instructeur_email} », en charge de la démarche « #{@group.procedure.libelle} ».
|
||||||
|
|
||||||
|
%p
|
||||||
|
Cliquez sur le lien ci-dessous pour voir la liste des instructeurs de ce groupe :
|
||||||
|
= link_to(@group.label, procedure_groupe_instructeur_url(@group.procedure, @group))
|
||||||
|
|
||||||
|
= render partial: "layouts/mailers/signature"
|
|
@ -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 }
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
.container
|
.container
|
||||||
.page-title
|
.page-title
|
||||||
Résultat de la recherche :
|
Résultat de la recherche :
|
||||||
= pluralize(@dossiers.count, "dossier trouvé", "dossiers trouvés")
|
= t('pluralize.dossier_trouve', count: @dossiers.count)
|
||||||
|
|
||||||
- if @dossiers.present?
|
- if @dossiers.present?
|
||||||
%table.table.dossiers-table.hoverable
|
%table.table.dossiers-table.hoverable
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -27,15 +27,21 @@
|
||||||
%p.missing-steps (à compléter)
|
%p.missing-steps (à compléter)
|
||||||
|
|
||||||
%a#onglet-administrateurs{ href: url_for(procedure_administrateurs_path(@procedure)) }
|
%a#onglet-administrateurs{ href: url_for(procedure_administrateurs_path(@procedure)) }
|
||||||
.procedure-list-element{ class: ('active' if active == 'Administrateurs') }
|
.procedure-list-element
|
||||||
Administrateurs
|
Administrateurs
|
||||||
|
|
||||||
|
- if !feature_enabled?(:routage)
|
||||||
%a#onglet-instructeurs{ href: url_for(admin_procedure_assigns_path(@procedure)) }
|
%a#onglet-instructeurs{ href: url_for(admin_procedure_assigns_path(@procedure)) }
|
||||||
.procedure-list-element{ class: ('active' if active == 'Instructeurs') }
|
.procedure-list-element{ class: ('active' if active == 'Instructeurs') }
|
||||||
Instructeurs
|
Instructeurs
|
||||||
- if @procedure.missing_steps.include?(:instructeurs)
|
- if @procedure.missing_steps.include?(:instructeurs)
|
||||||
%p.missing-steps (à compléter)
|
%p.missing-steps (à compléter)
|
||||||
|
|
||||||
|
- if feature_enabled?(:routage)
|
||||||
|
%a#onglet-instructeurs{ href: url_for(procedure_groupe_instructeurs_path(@procedure)) }
|
||||||
|
.procedure-list-element
|
||||||
|
Groupe d'instructeurs
|
||||||
|
|
||||||
- if !@procedure.locked?
|
- if !@procedure.locked?
|
||||||
%a#onglet-champs{ href: champs_procedure_path(@procedure) }
|
%a#onglet-champs{ href: champs_procedure_path(@procedure) }
|
||||||
.procedure-list-element{ class: ('active' if active == 'Champs') }
|
.procedure-list-element{ class: ('active' if active == 'Champs') }
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
= render partial: 'new_administrateur/breadcrumbs',
|
||||||
|
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
||||||
|
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
|
||||||
|
'Groupes d’instructeurs'] }
|
||||||
|
|
||||||
|
.container.groupe-instructeur
|
||||||
|
.card
|
||||||
|
= form_for @procedure,
|
||||||
|
url: { action: :update_routing_criteria_name },
|
||||||
|
html: { class: 'form' } do |f|
|
||||||
|
|
||||||
|
= f.label :routing_criteria_name do
|
||||||
|
Libellé du routage
|
||||||
|
%span.notice Ce texte apparaitra sur le formulaire usager comme le libellé d'une liste
|
||||||
|
= f.text_field :routing_criteria_name, placeholder: 'Votre ville', required: true
|
||||||
|
= f.submit 'Renommer', class: 'button primary send'
|
||||||
|
|
||||||
|
.card
|
||||||
|
.card-title Gestion des Groupes
|
||||||
|
|
||||||
|
= form_for :groupe_instructeur, html: { class: 'form' } do |f|
|
||||||
|
= f.label :label do
|
||||||
|
Ajouter un groupe
|
||||||
|
%span.notice Ce groupe sera un choix de la liste « #{@procedure.routing_criteria_name} » .
|
||||||
|
= f.text_field :label, placeholder: 'Ville de Bordeaux', required: true
|
||||||
|
= f.submit 'Ajouter le groupe', class: 'button primary send'
|
||||||
|
|
||||||
|
%table.table.mt-2
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th{ colspan: 2 } Liste des groupes
|
||||||
|
%tbody
|
||||||
|
- @groupes_instructeurs.each do |group|
|
||||||
|
%tr
|
||||||
|
%td= group.label
|
||||||
|
%td.actions= link_to "voir", procedure_groupe_instructeur_path(@procedure, group)
|
||||||
|
|
||||||
|
= paginate @groupes_instructeurs
|
|
@ -0,0 +1,47 @@
|
||||||
|
= render partial: 'new_administrateur/breadcrumbs',
|
||||||
|
locals: { steps: [link_to('Démarches', admin_procedures_path),
|
||||||
|
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
|
||||||
|
link_to('Groupes d’instructeurs', procedure_groupe_instructeurs_path(@procedure)),
|
||||||
|
@groupe_instructeur.label] }
|
||||||
|
|
||||||
|
.container.groupe-instructeur
|
||||||
|
.rename_form_block
|
||||||
|
.flex.baseline-start
|
||||||
|
%h1 Groupe « #{@groupe_instructeur.label} »
|
||||||
|
|
||||||
|
.card.mt-2
|
||||||
|
= form_for @groupe_instructeur,
|
||||||
|
url: procedure_groupe_instructeur_path(@procedure, @groupe_instructeur),
|
||||||
|
html: { class: 'form' } do |f|
|
||||||
|
|
||||||
|
= f.label :label, 'Nom du groupe'
|
||||||
|
= f.text_field :label, placeholder: 'Ville de Bordeaux', required: true
|
||||||
|
= f.submit 'Renommer', class: 'button primary send'
|
||||||
|
|
||||||
|
.card
|
||||||
|
.card-title Gestion des instructeurs
|
||||||
|
= form_for :instructeur,
|
||||||
|
url: { action: :add_instructeur },
|
||||||
|
html: { class: 'form' } do |f|
|
||||||
|
|
||||||
|
= f.label :email do
|
||||||
|
Affecter un nouvel instructeur
|
||||||
|
= f.email_field :email, placeholder: 'marie.dupont@exemple.fr', required: true
|
||||||
|
= f.submit 'Affecter', class: 'button primary send'
|
||||||
|
|
||||||
|
%table.table.mt-2
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th{ colspan: 2 } Instructeurs affectés
|
||||||
|
%tbody
|
||||||
|
- @instructeurs.each do |instructeur|
|
||||||
|
%tr
|
||||||
|
%td= instructeur.email
|
||||||
|
%td.actions= button_to 'retirer',
|
||||||
|
{ action: :remove_instructeur },
|
||||||
|
{ method: :delete,
|
||||||
|
data: { confirm: "Êtes-vous sûr de vouloir retirer l’instructeur « #{instructeur.email} » du groupe « #{@groupe_instructeur.label} » ?" },
|
||||||
|
params: { instructeur: { id: instructeur.id }},
|
||||||
|
class: 'button' }
|
||||||
|
|
||||||
|
= paginate @instructeurs
|
|
@ -93,7 +93,7 @@ Rails.application.configure do
|
||||||
# the I18n.default_locale when a translation cannot be found).
|
# the I18n.default_locale when a translation cannot be found).
|
||||||
config.i18n.fallbacks = true
|
config.i18n.fallbacks = true
|
||||||
|
|
||||||
config.active_storage.service = :proxied
|
config.active_storage.service = :openstack
|
||||||
|
|
||||||
# Send deprecation notices to registered listeners.
|
# Send deprecation notices to registered listeners.
|
||||||
config.active_support.deprecation = :notify
|
config.active_support.deprecation = :notify
|
||||||
|
|
|
@ -7,3 +7,33 @@ ActiveStorage::Service.url_expires_in = 1.hour
|
||||||
# cleaner (as it allows to enqueue the virus scan on attachment creation, rather
|
# cleaner (as it allows to enqueue the virus scan on attachment creation, rather
|
||||||
# than on blob creation).
|
# than on blob creation).
|
||||||
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }
|
ActiveSupport.on_load(:active_storage_blob) { include BlobVirusScanner }
|
||||||
|
|
||||||
|
# When an OpenStack service is initialized it makes a request to fetch
|
||||||
|
# `publicURL` to use for all operations. We intercept the method that reads
|
||||||
|
# this url and replace the host with DS_Proxy host. This way all the operation
|
||||||
|
# are performed through DS_Proxy.
|
||||||
|
#
|
||||||
|
# https://github.com/fog/fog-openstack/blob/37621bb1d5ca78d037b3c56bd307f93bba022ae1/lib/fog/openstack/auth/catalog/v2.rb#L16
|
||||||
|
require 'fog/openstack/auth/catalog/v2'
|
||||||
|
|
||||||
|
module Fog::OpenStack::Auth::Catalog
|
||||||
|
class V2
|
||||||
|
def endpoint_url(endpoint, interface)
|
||||||
|
url = endpoint["#{interface}URL"]
|
||||||
|
|
||||||
|
if interface == 'public'
|
||||||
|
publicize(url)
|
||||||
|
else
|
||||||
|
url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def publicize(url)
|
||||||
|
search = %r{^https://[^/]+/}
|
||||||
|
replace = 'https://static.demarches-simplifiees.fr/'
|
||||||
|
url.gsub(search, replace)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -313,3 +313,7 @@ fr:
|
||||||
zero: archivé
|
zero: archivé
|
||||||
one: archivé
|
one: archivé
|
||||||
other: archivés
|
other: archivés
|
||||||
|
dossier_trouve:
|
||||||
|
zero: 0 dossier trouvé
|
||||||
|
one: 1 dossier trouvé
|
||||||
|
other: "%{count} dossiers trouvés"
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -9,7 +9,7 @@ fr:
|
||||||
textarea: 'Zone de texte'
|
textarea: 'Zone de texte'
|
||||||
date: 'Date'
|
date: 'Date'
|
||||||
datetime: 'Date et Heure'
|
datetime: 'Date et Heure'
|
||||||
number: 'Nombre entier'
|
number: 'Nombre'
|
||||||
decimal_number: 'Nombre décimal'
|
decimal_number: 'Nombre décimal'
|
||||||
integer_number: 'Nombre entier'
|
integer_number: 'Nombre entier'
|
||||||
checkbox: 'Case à cocher'
|
checkbox: 'Case à cocher'
|
||||||
|
|
|
@ -350,6 +350,17 @@ Rails.application.routes.draw do
|
||||||
get 'annotations'
|
get 'annotations'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :groupe_instructeurs, only: [:index, :show, :create, :update] do
|
||||||
|
member do
|
||||||
|
post 'add_instructeur'
|
||||||
|
delete 'remove_instructeur'
|
||||||
|
end
|
||||||
|
|
||||||
|
collection do
|
||||||
|
patch 'update_routing_criteria_name'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :administrateurs, controller: 'procedure_administrateurs', only: [:index, :create, :destroy]
|
resources :administrateurs, controller: 'procedure_administrateurs', only: [:index, :create, :destroy]
|
||||||
|
|
||||||
resources :types_de_champ, only: [:create, :update, :destroy] do
|
resources :types_de_champ, only: [:create, :update, :destroy] do
|
||||||
|
|
|
@ -4,9 +4,6 @@ local:
|
||||||
test:
|
test:
|
||||||
service: Disk
|
service: Disk
|
||||||
root: <%= Rails.root.join("tmp/storage") %>
|
root: <%= Rails.root.join("tmp/storage") %>
|
||||||
proxied:
|
|
||||||
service: DsProxy
|
|
||||||
wrapped: openstack
|
|
||||||
openstack:
|
openstack:
|
||||||
service: OpenStack
|
service: OpenStack
|
||||||
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
|
container: "<%= ENV['FOG_ACTIVESTORAGE_DIRECTORY'] %>"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddDefaultValueToRoutingCriteriaName < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
change_column :procedures, :routing_criteria_name, :text, default: "Votre ville"
|
||||||
|
end
|
||||||
|
end
|
|
@ -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: 2019_10_14_160538) do
|
ActiveRecord::Schema.define(version: 2019_10_23_183120) 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"
|
||||||
|
@ -486,7 +486,7 @@ ActiveRecord::Schema.define(version: 2019_10_14_160538) do
|
||||||
t.string "path", null: false
|
t.string "path", null: false
|
||||||
t.string "declarative_with_state"
|
t.string "declarative_with_state"
|
||||||
t.text "monavis_embed"
|
t.text "monavis_embed"
|
||||||
t.text "routing_criteria_name"
|
t.text "routing_criteria_name", default: "Votre ville"
|
||||||
t.boolean "csv_export_queued"
|
t.boolean "csv_export_queued"
|
||||||
t.boolean "xlsx_export_queued"
|
t.boolean "xlsx_export_queued"
|
||||||
t.boolean "ods_export_queued"
|
t.boolean "ods_export_queued"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
describe NewAdministrateur::GroupeInstructeursController, type: :controller do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
let(:admin) { create(:administrateur) }
|
||||||
|
let(:procedure) { create(:procedure, :published, administrateurs: [admin]) }
|
||||||
|
let!(:gi_1_1) { procedure.defaut_groupe_instructeur }
|
||||||
|
|
||||||
|
let(:procedure2) { create(:procedure, :published) }
|
||||||
|
let!(:gi_2_2) { procedure2.groupe_instructeurs.create(label: 'groupe instructeur 2 2') }
|
||||||
|
|
||||||
|
before { sign_in(admin.user) }
|
||||||
|
|
||||||
|
describe '#index' do
|
||||||
|
context 'of a procedure I own' do
|
||||||
|
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
|
||||||
|
|
||||||
|
before { get :index, params: { procedure_id: procedure.id } }
|
||||||
|
|
||||||
|
context 'when a procedure has multiple groups' do
|
||||||
|
it { expect(response).to have_http_status(:ok) }
|
||||||
|
it { expect(response.body).to include(gi_1_1.label) }
|
||||||
|
it { expect(response.body).to include(gi_1_2.label) }
|
||||||
|
it { expect(response.body).not_to include(gi_2_2.label) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#show' do
|
||||||
|
context 'of a group I belong to' do
|
||||||
|
before { get :show, params: { procedure_id: procedure.id, id: gi_1_1.id } }
|
||||||
|
|
||||||
|
it { expect(response).to have_http_status(:ok) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#create' do
|
||||||
|
before do
|
||||||
|
post :create,
|
||||||
|
params: {
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
groupe_instructeur: { label: label }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a valid name' do
|
||||||
|
let(:label) { "nouveau_groupe" }
|
||||||
|
|
||||||
|
it { expect(flash.notice).to be_present }
|
||||||
|
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, procedure.groupe_instructeurs.last)) }
|
||||||
|
it { expect(procedure.groupe_instructeurs.count).to eq(2) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an invalid group name' do
|
||||||
|
let(:label) { gi_1_1.label }
|
||||||
|
|
||||||
|
it { expect(response).to render_template(:index) }
|
||||||
|
it { expect(procedure.groupe_instructeurs.count).to eq(1) }
|
||||||
|
it { expect(flash.alert).to be_present }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#update' do
|
||||||
|
let(:new_name) { 'nouveau nom du groupe' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
patch :update,
|
||||||
|
params: {
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
id: gi_1_1.id,
|
||||||
|
groupe_instructeur: { label: new_name }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(gi_1_1.reload.label).to eq(new_name) }
|
||||||
|
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) }
|
||||||
|
it { expect(flash.notice).to be_present }
|
||||||
|
|
||||||
|
context 'when the name is already taken' do
|
||||||
|
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
|
||||||
|
let(:new_name) { gi_1_2.label }
|
||||||
|
|
||||||
|
it { expect(gi_1_1.reload.label).not_to eq(new_name) }
|
||||||
|
it { expect(flash.alert).to be_present }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#add_instructeur' do
|
||||||
|
let!(:instructeur) { create(:instructeur) }
|
||||||
|
before do
|
||||||
|
gi_1_1.instructeurs << instructeur
|
||||||
|
|
||||||
|
post :add_instructeur,
|
||||||
|
params: {
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
id: gi_1_1.id,
|
||||||
|
instructeur: { email: new_instructeur_email }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'of a new instructeur' do
|
||||||
|
let(:new_instructeur_email) { 'new_instructeur@mail.com' }
|
||||||
|
|
||||||
|
it { expect(gi_1_1.instructeurs.pluck(:email)).to include(new_instructeur_email) }
|
||||||
|
it { expect(flash.notice).to be_present }
|
||||||
|
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'of an instructeur already in the group' do
|
||||||
|
let(:new_instructeur_email) { instructeur.email }
|
||||||
|
|
||||||
|
it { expect(flash.alert).to be_present }
|
||||||
|
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, procedure.defaut_groupe_instructeur)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#remove_instructeur' do
|
||||||
|
let!(:instructeur) { create(:instructeur) }
|
||||||
|
|
||||||
|
before { gi_1_1.instructeurs << admin.instructeur << instructeur }
|
||||||
|
|
||||||
|
def remove_instructeur(email)
|
||||||
|
delete :remove_instructeur,
|
||||||
|
params: {
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
id: gi_1_1.id,
|
||||||
|
instructeur: { id: admin.instructeur.id }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are many instructeurs' do
|
||||||
|
before { remove_instructeur(admin.user.email) }
|
||||||
|
|
||||||
|
it { expect(gi_1_1.instructeurs).to include(instructeur) }
|
||||||
|
it { expect(gi_1_1.reload.instructeurs.count).to eq(1) }
|
||||||
|
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there is only one instructeur' do
|
||||||
|
before do
|
||||||
|
remove_instructeur(admin.user.email)
|
||||||
|
remove_instructeur(instructeur.email)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(gi_1_1.instructeurs).to include(instructeur) }
|
||||||
|
it { expect(gi_1_1.instructeurs.count).to eq(1) }
|
||||||
|
it { expect(flash.alert).to eq('Suppression impossible : il doit y avoir au moins un instructeur dans le groupe') }
|
||||||
|
it { expect(response).to redirect_to(procedure_groupe_instructeur_path(procedure, gi_1_1)) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#update_routing_criteria_name' do
|
||||||
|
before do
|
||||||
|
patch :update_routing_criteria_name,
|
||||||
|
params: {
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
procedure: { routing_criteria_name: 'new name !' }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(procedure.reload.routing_criteria_name).to eq('new name !') }
|
||||||
|
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)
|
||||||
|
|
126
spec/features/routing/full_scenario_spec.rb
Normal file
126
spec/features/routing/full_scenario_spec.rb
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
feature 'The routing' do
|
||||||
|
let(:procedure) { create(:procedure, :with_service, :for_individual) }
|
||||||
|
let(:administrateur) { create(:administrateur, procedures: [procedure]) }
|
||||||
|
let(:scientifique_user) { create(:user) }
|
||||||
|
let(:litteraire_user) { create(:user) }
|
||||||
|
|
||||||
|
before { Flipper.enable_actor(:routage, administrateur.user) }
|
||||||
|
|
||||||
|
scenario 'works' do
|
||||||
|
login_as administrateur.user, scope: :user
|
||||||
|
|
||||||
|
visit admin_procedure_path(procedure.id)
|
||||||
|
click_on "Groupe d'instructeurs"
|
||||||
|
|
||||||
|
# rename routing criteria to spécialité
|
||||||
|
fill_in 'procedure_routing_criteria_name', with: 'spécialité'
|
||||||
|
click_on 'Renommer'
|
||||||
|
expect(procedure.reload.routing_criteria_name).to eq('spécialité')
|
||||||
|
|
||||||
|
# rename defaut groupe to littéraire
|
||||||
|
click_on 'voir'
|
||||||
|
fill_in 'groupe_instructeur_label', with: 'littéraire'
|
||||||
|
click_on 'Renommer'
|
||||||
|
|
||||||
|
# add victor to littéraire groupe
|
||||||
|
fill_in 'instructeur_email', with: 'victor@inst.com'
|
||||||
|
perform_enqueued_jobs { click_on 'Affecter' }
|
||||||
|
victor = User.find_by(email: 'victor@inst.com').instructeur
|
||||||
|
|
||||||
|
click_on "Groupes d’instructeurs"
|
||||||
|
|
||||||
|
# add scientifique groupe
|
||||||
|
fill_in 'groupe_instructeur_label', with: 'scientifique'
|
||||||
|
click_on 'Ajouter le groupe'
|
||||||
|
|
||||||
|
# add marie to scientifique groupe
|
||||||
|
fill_in 'instructeur_email', with: 'marie@inst.com'
|
||||||
|
perform_enqueued_jobs { click_on 'Affecter' }
|
||||||
|
marie = User.find_by(email: 'marie@inst.com').instructeur
|
||||||
|
|
||||||
|
# publish
|
||||||
|
publish_procedure(procedure)
|
||||||
|
log_out
|
||||||
|
|
||||||
|
# 2 users fill a dossier in each group
|
||||||
|
user_send_dossier(scientifique_user, 'scientifique')
|
||||||
|
user_send_dossier(litteraire_user, 'littéraire')
|
||||||
|
|
||||||
|
# the litteraires instructeurs only manage the litteraires dossiers
|
||||||
|
register_instructeur_and_log_in(victor.email)
|
||||||
|
click_on procedure.libelle
|
||||||
|
expect(page).to have_text(litteraire_user.email)
|
||||||
|
expect(page).not_to have_text(scientifique_user.email)
|
||||||
|
|
||||||
|
# the search only show litteraires dossiers
|
||||||
|
fill_in 'q', with: scientifique_user.email
|
||||||
|
click_on 'Rechercher'
|
||||||
|
expect(page).to have_text('0 dossier trouvé')
|
||||||
|
|
||||||
|
fill_in 'q', with: litteraire_user.email
|
||||||
|
click_on 'Rechercher'
|
||||||
|
expect(page).to have_text('1 dossier trouvé')
|
||||||
|
|
||||||
|
## and the result is clickable
|
||||||
|
click_on litteraire_user.email
|
||||||
|
expect(page).to have_current_path(instructeur_dossier_path(procedure, litteraire_user.dossiers.first))
|
||||||
|
|
||||||
|
log_out
|
||||||
|
|
||||||
|
# the scientifiques instructeurs only manage the scientifiques dossiers
|
||||||
|
register_instructeur_and_log_in(marie.email)
|
||||||
|
click_on procedure.libelle
|
||||||
|
expect(page).not_to have_text(litteraire_user.email)
|
||||||
|
expect(page).to have_text(scientifique_user.email)
|
||||||
|
log_out
|
||||||
|
|
||||||
|
# TODO: notifications tests
|
||||||
|
end
|
||||||
|
|
||||||
|
def publish_procedure(procedure)
|
||||||
|
click_on procedure.libelle
|
||||||
|
find('#publish-procedure').click
|
||||||
|
within '#publish-modal' do
|
||||||
|
fill_in 'lien_site_web', with: 'http://some.website'
|
||||||
|
click_on 'publish'
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to have_text('Démarche publiée')
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_send_dossier(user, groupe)
|
||||||
|
login_as user, scope: :user
|
||||||
|
visit commencer_path(path: procedure.reload.path)
|
||||||
|
click_on 'Commencer la démarche'
|
||||||
|
|
||||||
|
fill_in 'individual_nom', with: 'Nom'
|
||||||
|
fill_in 'individual_prenom', with: 'Prenom'
|
||||||
|
click_button('Continuer')
|
||||||
|
|
||||||
|
select(groupe, from: 'dossier_groupe_instructeur_id')
|
||||||
|
|
||||||
|
click_on 'Déposer le dossier'
|
||||||
|
|
||||||
|
log_out
|
||||||
|
end
|
||||||
|
|
||||||
|
def register_instructeur_and_log_in(email)
|
||||||
|
confirmation_email = emails_sent_to(email)
|
||||||
|
.filter { |m| m.subject == 'Activez votre compte instructeur' }
|
||||||
|
.first
|
||||||
|
token_params = confirmation_email.body.match(/token=[^"]+/)
|
||||||
|
|
||||||
|
visit "users/activate?#{token_params}"
|
||||||
|
fill_in :user_password, with: 'démarches-simplifiées-pwd'
|
||||||
|
|
||||||
|
click_button 'Définir le mot de passe'
|
||||||
|
|
||||||
|
expect(page).to have_content 'Mot de passe enregistré'
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_out
|
||||||
|
click_on 'Se déconnecter'
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,10 +15,11 @@ describe DossierLinkHelper do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when access as instructeur" do
|
context "when access as instructeur" do
|
||||||
let(:dossier) { create(:dossier) }
|
let(:procedure) { create(:procedure, :routee) }
|
||||||
|
let(:dossier) { create(:dossier, groupe_instructeur: procedure.groupe_instructeurs.last) }
|
||||||
let(:instructeur) { create(:instructeur) }
|
let(:instructeur) { create(:instructeur) }
|
||||||
|
|
||||||
before { dossier.procedure.defaut_groupe_instructeur.instructeurs << instructeur }
|
before { procedure.groupe_instructeurs.last.instructeurs << instructeur }
|
||||||
|
|
||||||
it { expect(helper.dossier_linked_path(instructeur, dossier)).to eq(instructeur_dossier_path(dossier.procedure, dossier)) }
|
it { expect(helper.dossier_linked_path(instructeur, dossier)).to eq(instructeur_dossier_path(dossier.procedure, dossier)) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -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)) }
|
||||||
|
|
38
spec/models/groupe_instructeur_spec.rb
Normal file
38
spec/models/groupe_instructeur_spec.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe GroupeInstructeur, type: :model do
|
||||||
|
let(:procedure) { create(:procedure) }
|
||||||
|
subject { GroupeInstructeur.new(label: label, procedure: procedure) }
|
||||||
|
|
||||||
|
context 'with no label provided' do
|
||||||
|
let(:label) { '' }
|
||||||
|
|
||||||
|
it { is_expected.to be_invalid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a valid label' do
|
||||||
|
let(:label) { 'Préfecture de la Marne' }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a label with extra spaces' do
|
||||||
|
let(:label) { 'Préfecture de la Marne ' }
|
||||||
|
before do
|
||||||
|
subject.save
|
||||||
|
subject.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
it { expect(subject.label).to eq("Préfecture de la Marne") }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a label already used for this procedure' do
|
||||||
|
let(:label) { 'Préfecture de la Marne' }
|
||||||
|
before do
|
||||||
|
GroupeInstructeur.create!(label: label, procedure: procedure)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_invalid }
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue