Merge pull request #8940 from demarches-simplifiees/routing-new-ux

Nouvelle UX pour le routage
This commit is contained in:
Eric Leroy-Terquem 2023-05-31 10:16:56 +00:00 committed by GitHub
commit bbedf0e659
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 917 additions and 714 deletions

View file

@ -42,6 +42,10 @@
&.row-reverse {
flex-direction: row-reverse;
}
&.auto {
flex: auto;
}
}
.flex-grow {

View file

@ -2,10 +2,11 @@ class Dsfr::SidemenuComponent < ApplicationComponent
renders_many :links, "LinkComponent"
class LinkComponent < ApplicationComponent
attr_reader :name, :url
def initialize(name:, url:)
attr_reader :name, :url, :icon
def initialize(name:, url:, icon: nil)
@name = name
@url = url
@icon = icon
end
end

View file

@ -6,4 +6,8 @@
%ul.fr-sidemenu__list
- links.each do |link|
%li{ class: "fr-sidemenu__item fr-sidemenu__item#{active?(link.url) ? '--active' : ''}" }
= link_to link.name, link.url, class: 'fr-sidemenu__link', 'aria-current': active?(link.url) ? 'page' : nil, target: "_self"
= link_to link.url, class: 'fr-sidemenu__link ', 'aria-current': active?(link.url) ? 'page' : nil, target: "_self" do
- capture do
- if link.icon.present?
%span{ class: link.icon } 
= link.name

View file

@ -0,0 +1,6 @@
class Procedure::GroupesAjoutComponent < ApplicationComponent
def initialize(procedure:, groupe_instructeurs:)
@procedure = procedure
@groupe_instructeurs = groupe_instructeurs
end
end

View file

@ -0,0 +1,6 @@
---
fr:
add_a_group:
title: Nouveau groupe
button:
add_group: Ajouter

View file

@ -0,0 +1,15 @@
- content_for(:title, 'Ajout de groupes')
%h1 Ajout de groupes d'instructeurs
= render partial: 'administrateurs/groupe_instructeurs/import_export',
locals: { procedure: @procedure,
groupe_instructeurs: @groupe_instructeurs }
%section
= form_for :groupe_instructeur,
method: :post do |f|
= f.label :label, class: 'fr-label fr-mb-1w' do
= t('.add_a_group.title')
.flex.justify-between.align-baseline.fr-mb-1w
= f.text_field :label, required: true, class: 'fr-input', placeholder: 'Entrer un nom de groupe'
= f.button t('.button.add_group'), class: "fr-btn fr-btn fr-btn--secondary fr-btn--icon-right fr-icon-add-line ml-2"

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
class Procedure::GroupesManagementComponent < ApplicationComponent
def initialize(procedure:, groupe_instructeurs:, query:)
@procedure = procedure
@groupe_instructeurs = groupe_instructeurs
@query = query
@total = groupe_instructeurs.total_count
end
def table_header
if @query.present?
if @groupe_instructeurs.length != @total
"#{t('.groupe', count: @groupe_instructeurs.length)} sur #{@total} #{t('.found', count: @total)}"
else
"#{t('.groupe', count: @groupe_instructeurs.length)} #{t('.found', count: @groupe_instructeurs.length)}"
end
else
if @groupe_instructeurs.length != @total
"#{t('.groupe', count: @groupe_instructeurs.length)} sur #{@total}"
else
t('.groupe', count: @groupe_instructeurs.length)
end
end
end
end

View file

@ -0,0 +1,8 @@
---
fr:
groupe:
one: "%{count} groupe"
other: "%{count} groupes"
found:
one: "trouvé"
other: "trouvés"

View file

@ -0,0 +1,40 @@
- content_for(:title, 'Groupes')
%h1 Gestion des groupes
= render Procedure::GroupesSearchComponent.new(procedure: @procedure, query: @query, to_configure_count: @procedure.groupe_instructeurs.filter(&:routing_to_configure?).count)
.fr-table.fr-table--no-caption.fr-table--layout-fixed.fr-mt-2w
%table
%caption= table_header
%thead
%tr
%th{ scope: "col" }
.flex
.flex.auto= table_header
%span.fr-icon.fr-icon-user-line
%tbody
- @groupe_instructeurs.each do |gi|
%tr
%td
.flex
.flex.auto
= link_to admin_procedure_groupe_instructeur_path(@procedure, gi), class: 'fr-link' do
%span= gi.label
- if gi.closed
%p.fr-badge.fr-badge--info.fr-badge--sm.fr-ml-1w inactif
- elsif gi.routing_to_configure?
%p.fr-badge.fr-badge--warning.fr-badge--sm.fr-ml-1w à configurer
%div{ style: 'width: 25px; text-align: center;' }
#{gi.instructeurs.count}
%p= gi.routing_rule&.to_s(@procedure.active_revision.types_de_champ)
.fr-mt-1w
= paginate @groupe_instructeurs
= form_tag admin_procedure_update_defaut_groupe_instructeur_path,
class: 'fr-my-3w',
data: { controller: 'autosave' } do
= label_tag :defaut_groupe_instructeur_id, 'Et si aucune règle ne correspond, router vers :', class: 'fr-label'
= select_tag :defaut_groupe_instructeur_id,
options_for_select(@procedure.groupe_instructeurs.pluck(:label, :id), selected: @procedure.defaut_groupe_instructeur.id),
class: 'fr-select'

View file

@ -0,0 +1,5 @@
class Procedure::GroupesSearchComponent < ApplicationComponent
def initialize(procedure:, query:, to_configure_count:)
@procedure, @query, @to_configure_count = procedure, query, to_configure_count
end
end

View file

@ -0,0 +1,9 @@
= form_for admin_procedure_groupe_instructeurs_path(@procedure),
method: :get do
#header-search.fr-search-bar.fr-mb-2w{ role: "search" }
= label_tag :q, 'Rechercher par nom', class: 'fr-label'
= text_field_tag :q, @query, class: 'fr-input', type: 'search', autocomplete: 'off', placeholder: 'Rechercher par nom'
%button.fr-btn{ title: "Rechercher" } Rechercher
- if @to_configure_count > 0
%span #{ @to_configure_count } à configurer

View file

@ -0,0 +1,9 @@
class Procedure::InstructeursManagementComponent < ApplicationComponent
def initialize(procedure:, groupe_instructeur:, instructeurs:, available_instructeur_emails:, disabled_as_super_admin:)
@procedure = procedure
@groupe_instructeur = groupe_instructeur
@instructeurs = instructeurs
@available_instructeur_emails = available_instructeur_emails
@disabled_as_super_admin = disabled_as_super_admin
end
end

View file

@ -0,0 +1,13 @@
- content_for(:title, 'Instructeurs')
%h1 Gestion des instructeurs
= render partial: 'administrateurs/groupe_instructeurs/import_export',
locals: { procedure: @procedure,
groupe_instructeurs: @procedure.groupe_instructeurs }
= render partial: 'administrateurs/groupe_instructeurs/instructeurs',
locals: { procedure: @procedure,
groupe_instructeur: @groupe_instructeur,
instructeurs: @instructeurs,
available_instructeur_emails: @available_instructeur_emails,
disabled_as_super_admin: @disabled_as_super_admin }

View file

@ -0,0 +1,22 @@
class Procedure::InstructeursMenuComponent < ApplicationComponent
def initialize(procedure:)
@procedure = procedure
end
private
def links
first_option, first_icon = if @procedure.groupe_instructeurs.one?
instructeurs_count = @procedure.groupe_instructeurs.first.instructeurs.count
[t('.instructeurs', count: instructeurs_count), 'fr-icon-user-line']
else
["#{@procedure.groupe_instructeurs.count} groupes", 'fr-icon-group-line']
end
[
{ name: first_option, url: admin_procedure_groupe_instructeurs_path(@procedure), icon: "#{first_icon} fr-icon--sm" },
({ name: 'Ajout de groupes', url: ajout_admin_procedure_groupe_instructeurs_path(@procedure), icon: 'fr-icon-add-circle-line fr-icon--sm' } if @procedure.routing_enabled?),
{ name: 'Options', url: options_admin_procedure_groupe_instructeurs_path(@procedure), icon: 'fr-icon-settings-5-line fr-icon--sm' }
].compact
end
end

View file

@ -0,0 +1,5 @@
---
fr:
instructeurs:
one: "%{count} instructeur"
other: "%{count} instructeurs"

View file

@ -0,0 +1,7 @@
.container
.fr-grid-row
.fr-col.fr-col-12.fr-col-md-3
= render(Dsfr::SidemenuComponent.new) do |component|
- component.with_links(links)
.fr-col= content

View file

@ -0,0 +1,6 @@
class Procedure::InstructeursOptionsComponent < ApplicationComponent
def initialize(procedure:, state:)
@procedure = procedure
@state = state
end
end

View file

@ -0,0 +1,11 @@
---
fr:
routing_configuration_notice_1:
Le routage permet dacheminer les dossiers vers différents groupes dinstructeurs.
routing_configuration_notice_2_html: |
<p>Pour le configurer, votre formulaire doit comporter
au moins un champ « choix simple ».</p>
<p>Ajoutez ce champ dans la page <a href="%{path}">« Configuration des champs »</a>.</p>
delete_title: Aucun dossier ne sera supprimé. Les groupes d'instructeurs vont être supprimés. Seuls les instructeurs du groupe « %{defaut_label} » resteront affectés à la procédure.
delete_confirmation: |
Attention : tous les dossiers vont être déplacés dans le groupe « %{defaut_label} » et seuls les instructeurs présent dans ce groupe resteront affectés à la procédure. Souhaitez-vous continuer ?

View file

@ -0,0 +1,49 @@
- content_for(:title, 'Options')
- if @state.nil?
%h1 Options concernant linstruction
%ul.fr-toggle__list
%li
= form_for @procedure,
method: :patch,
url: update_instructeurs_self_management_enabled_admin_procedure_groupe_instructeurs_path(@procedure),
data: { controller: 'autosubmit', turbo: 'true' } do |f|
= render Dsfr::ToggleComponent.new(form: f,
target: :instructeurs_self_management_enabled,
title: 'Autogestion des instructeurs',
hint: "Lautogestion des instructeurs permet aux instructeurs de gérer eux-mêmes la liste des instructeurs de la démarche.#{ ' Lorsque la démarche est routée, lautogestion est activée doffice et nest pas désactivable.' if @procedure.routing_enabled? }",
disabled: @procedure.routing_enabled?)
%p.fr-mt-2w Routage
%p.fr-mt-2w= t('.routing_configuration_notice_1')
- if @procedure.active_revision.routable_types_de_champ.none?
%p.fr-mt-2w= t('.routing_configuration_notice_2_html', path: champs_admin_procedure_path(@procedure))
- elsif @procedure.groupe_instructeurs.active.one?
= link_to 'Configurer le routage', options_admin_procedure_groupe_instructeurs_path(@procedure, state: :choix), class: 'fr-btn'
- else
= button_to 'Supprimer le routage',
destroy_all_groups_but_defaut_admin_procedure_groupe_instructeurs_path,
class: 'fr-btn',
method: :delete,
title: t('.delete_title', defaut_label: @procedure.defaut_groupe_instructeur.label),
data: ( @procedure.publiee? ? { disable_with: "Suppression...", confirm: t('.delete_confirmation', defaut_label: @procedure.defaut_groupe_instructeur.label) } : nil)
- elsif @state == 'choix'
= form_for :choice,
method: :patch,
data: { controller: 'radio-enabled-submit' },
url: wizard_admin_procedure_groupe_instructeurs_path(@procedure) do |f|
%div{ data: { 'action': "click->radio-enabled-submit#click" } }
= render Dsfr::RadioButtonListComponent.new(form: f,
target: :state,
buttons: [ { label: 'À partir dun champ', value: 'routage_simple', hint: 'crée les groupes en fonction dun champ du formulaire' } ,
{ label: 'Avancé', value: 'routage_custom', hint: 'libre à vous de créer et de configurer les groupes' }]) do
%h1 Choix du type de routage
%ul.fr-btns-group.fr-btns-group--inline-sm
%li
= link_to 'Retour', options_admin_procedure_groupe_instructeurs_path(@procedure), class: 'fr-btn fr-btn--secondary'
%li
%button.fr-btn{ disabled: true, data: { 'radio-enabled-submit-target': 'submit' } } Continuer

View file

@ -0,0 +1,65 @@
class Procedure::OneGroupeManagementComponent < ApplicationComponent
include Logic
def initialize(revision:, groupe_instructeur:)
@revision = revision
@groupe_instructeur = groupe_instructeur
@procedure_id = revision.procedure_id
end
private
def targeted_champ
@groupe_instructeur.routing_rule&.left || empty
end
def value
@groupe_instructeur.routing_rule&.right || empty
end
def targeted_champ_tag
select_tag(
'targeted_champ',
options_for_select(targeted_champs_for_select, selected: targeted_champ.to_json),
class: 'fr-select'
)
end
def targeted_champs_for_select
empty_target_for_select + available_targets_for_select
end
def empty_target_for_select
[[t('.select'), empty.to_json]]
end
def available_targets_for_select
@revision
.routable_types_de_champ
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
end
def value_tag
select_tag(
'value',
options_for_select(
values_for_select(targeted_champ),
selected: value.to_json
),
class: 'fr-select'
)
end
def values_for_select(targeted_champ)
(empty_target_for_select + available_values_for_select(targeted_champ))
# add id to help morph render selected option
.map { |(libelle, json)| [libelle, json, { id: "option-#{libelle}" }] }
end
def available_values_for_select(targeted_champ)
return [] if targeted_champ.is_a?(Logic::Empty)
targeted_champ
.options(@revision.types_de_champ_public)
.map { |tdc| [tdc.first, constant(tdc.first).to_json] }
end
end

View file

@ -0,0 +1,8 @@
---
fr:
select: Sélectionner
move_files_confirmation: Réaffecter les dossiers à un autre groupe afin de pouvoir le supprimer
move_files:
zero: Déplacer les dossiers en brouillon
one: Déplacer un dossier
other: Déplacer les %{count} dossiers

View file

@ -0,0 +1,55 @@
%div{ id: dom_id(@groupe_instructeur, :routing) }
%h2 Paramètres principaux
= form_for @groupe_instructeur,
url: admin_procedure_groupe_instructeur_path(@procedure_id, @groupe_instructeur),
method: :patch do |f|
= f.label :label, 'Nom du groupe', class: 'fr-label fr-mb-1w'
.flex
= f.text_field :label, required: true, class: 'fr-input flex auto fr-mr-2w'
= f.button 'Renommer', class: 'fr-btn fr-btn--secondary'
= form_for @groupe_instructeur,
url: admin_procedure_groupe_instructeur_update_state_path(@procedure_id, @groupe_instructeur),
method: :patch,
data: { turbo: true, controller: 'autosubmit' } do |f|
.fr-checkbox-group.fr-my-3w
= f.check_box :closed, { id: 'closed', "aria-describedby" => "closed-messages", :name => "closed" }
%label.fr-label{ :for => "closed" }
Groupe inactif
%span.fr-hint-text Si cette option est activée, les usagers ne pourront plus sélectionner ce groupe d'instructeurs
= form_tag admin_procedure_routing_rules_path(@procedure_id),
method: :post,
data: { controller: 'autosave' },
class: 'fr-mb-3w' do
= hidden_field_tag('groupe_instructeur_id', @groupe_instructeur.id)
.flex
%p.fr-mb-1w.fr-mr-2w Routage
- if @groupe_instructeur.routing_to_configure?
%p.fr-mb-1w.fr-badge.fr-badge--warning.fr-badge--sm à configurer
.flex.align-baseline
.fr-mr-2w si le champ
.target.fr-mr-2w
= targeted_champ_tag
.operator.fr-mr-2w est égal à
.value
= value_tag
%ul.fr-btns-group.fr-btns-group--sm.fr-btns-group--inline.fr-btns-group--icon-right
%li
- if @groupe_instructeur.can_delete?
%p Supprimer le groupe
= button_to admin_procedure_groupe_instructeur_path(@procedure_id, @groupe_instructeur),
class: 'fr-btn fr-btn--tertiary fr-btn--icon-left fr-icon-delete-line',
method: :delete do
Supprimer
- else
= button_to reaffecter_dossiers_admin_procedure_groupe_instructeur_path(@procedure_id, @groupe_instructeur),
class: 'fr-btn fr-btn--tertiary fr-icon-folder-2-line',
title: t('.move_files_confirmation'),
method: :get do
= t('.move_files', count: @groupe_instructeur.dossiers.visible_by_administration.size)

View file

@ -24,6 +24,7 @@ fr:
update_type_champ: Le type du champ « %{label} » a été modifié. Il est maintenant de type « %{to} ».
update_piece_justificative_template: Le modèle de pièce justificative du champ « %{label} » a été modifié.
update_drop_down_options: "Les options de sélection du champ « %{label} » ont été modifiées :"
update_drop_down_options_alert: "Le champ « %{label} » est utilisé pour le routage des dossiers. Veuillez mettre à jour la configuration des groupes d'instructeurs après avoir publié les modifications."
enable_mandatory: Le champ « %{label} » est maintenant obligatoire.
disable_mandatory: Le champ « %{label} » nest plus obligatoire.
enable_drop_down_other: Le champ « %{label} » comporte maintenant un choix « Autre ».

View file

@ -74,6 +74,9 @@
- if !total_dossiers.zero? && !change.can_rebase?
.fr-alert.fr-alert--warning.fr-mt-1v
%p= t('.breaking_change', count: total_dossiers)
- if removed.present? && change.type_de_champ.used_by_routing_rules?
.fr-alert.fr-alert--warning.fr-mt-1v
= t(".#{prefix}.update_drop_down_options_alert", label: change.label)
- when :drop_down_other
- if change.from == false
- list.with_item do

View file

@ -1,82 +0,0 @@
class Procedure::RoutingRulesComponent < ApplicationComponent
include Logic
def initialize(revision:, groupe_instructeurs:)
@revision = revision
@groupe_instructeurs = groupe_instructeurs
@procedure_id = revision.procedure_id
end
def rows
@groupe_instructeurs.active.map do |gi|
if gi.routing_rule.present?
[gi.routing_rule.left, gi.routing_rule.right, gi]
else
[empty, empty, gi]
end
end
end
def can_route?
available_targets_for_select.present?
end
def targeted_champ_tag(targeted_champ, row_index)
select_tag(
'targeted_champ',
options_for_select(targeted_champs_for_select, selected: targeted_champ.to_json),
id: input_id_for('targeted_champ', row_index)
)
end
def value_tag(targeted_champ, value, row_index)
select_tag(
'value',
options_for_select(
values_for_select(targeted_champ, row_index),
selected: value.to_json
),
id: input_id_for('value', row_index)
)
end
def hidden_groupe_instructeur_tag(groupe_instructeur_id)
hidden_field_tag(
'groupe_instructeur_id',
groupe_instructeur_id
)
end
private
def targeted_champs_for_select
empty_target_for_select + available_targets_for_select
end
def empty_target_for_select
[[t('.select'), empty.to_json]]
end
def available_targets_for_select
@revision.types_de_champ_public
.filter { |tdc| [:drop_down_list].include?(tdc.type_champ.to_sym) }
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
end
def available_values_for_select(targeted_champ)
return [] if targeted_champ.is_a?(Logic::Empty)
targeted_champ
.options(@revision.types_de_champ_public)
.map { |tdc| [tdc.first, constant(tdc.first).to_json] }
end
def values_for_select(targeted_champ, row_index)
(empty_target_for_select + available_values_for_select(targeted_champ))
# add id to help morph render selected option
.map { |(libelle, json)| [libelle, json, { id: "#{row_index}-option-#{libelle}" }] }
end
def input_id_for(name, row_index)
"#{name}-#{row_index}"
end
end

View file

@ -1,11 +0,0 @@
---
fr:
select: Sélectionner
apply_routing_rules: Règles de routage
routing_rules_notice_html: |
<p>Ajoutez des règles de routage à partir de champs « choix simple » créés dans le <a href="%{path}">formulaire</a>.</p>
<p>Les dossiers seront routées vers le premier groupe affiché dont la règle correspond.</p>
routing_rules_warning_html: |
<p>Pour appliquer des règles de routage, votre formulaire doit comporter
au moins un champ « choix simple ».</p>
<p>Ajoutez ce champ dans la page <a href="%{path}">« Configuration des champs »</a>.</p>

View file

@ -1,40 +0,0 @@
.card#routing-rules
%h2.card-title= t('.apply_routing_rules')
- if can_route?
.notice
= t('.routing_rules_notice_html', path: champs_admin_procedure_path(@procedure_id))
.mt-2.width-100
%table.routing-rules-table.mt-2.width-100
%thead
%tr
%th.far-left Router vers
%th.if
%th.target Champ cible du routage
%th.operator
%th.value Valeur
.mt-2.width-100
- rows.each.with_index do |(targeted_champ, value, groupe_instructeur), row_index|
= form_tag admin_procedure_routing_rules_path(@procedure_id),
method: :post,
class: "form width-100 gi-#{groupe_instructeur.id}",
data: { controller: 'autosave' } do
= hidden_groupe_instructeur_tag(groupe_instructeur.id)
%table.routing-rules-table.condition-table.mt-2.width-100
%tbody
%tr
%td.far-left= groupe_instructeur.label
%td.if si
%td.target= targeted_champ_tag(targeted_champ, row_index)
%td.operator est égal à
%td.value= value_tag(targeted_champ, value, row_index)
= form_tag admin_procedure_update_defaut_groupe_instructeur_path(@procedure_id),
class: 'form flex align-baseline defaut-groupe',
data: { controller: 'autosave' } do
= label_tag :defaut_groupe_instructeur_id, 'Et si aucune règle ne correspond, router vers :'
= select_tag :defaut_groupe_instructeur_id,
options_for_select(@groupe_instructeurs.pluck(:label, :id), selected: @revision.procedure.defaut_groupe_instructeur.id),
class: 'width-100'
- else
.notice= t('.routing_rules_warning_html', path: champs_admin_procedure_path(@procedure_id))

View file

@ -1,6 +1,7 @@
module Administrateurs
class GroupeInstructeursController < AdministrateurController
include ActiveSupport::NumberHelper
include Logic
before_action :ensure_not_super_admin!, only: [:add_instructeur]
@ -19,6 +20,73 @@ module Administrateurs
@available_instructeur_emails = available_instructeur_emails
end
def options
@procedure = procedure
end
def ajout
redirect_to admin_procedure_groupe_instructeurs_path(procedure) if procedure.groupe_instructeurs.one?
@procedure = procedure
@groupes_instructeurs = paginated_groupe_instructeurs
end
def simple_routing
@procedure = procedure
end
def create_simple_routing
@procedure = procedure
stable_id = params[:create_simple_routing][:stable_id].to_i
tdc = @procedure.active_revision.routable_types_de_champ.find { |tdc| tdc.stable_id == stable_id }
tdc_options = tdc.options["drop_down_options"].reject(&:empty?)
tdc_options.each do |option_label|
gi = procedure.groupe_instructeurs.find_by({ label: option_label }) || procedure.groupe_instructeurs
.create({ label: option_label, instructeurs: [current_administrateur.instructeur] })
gi.update(routing_rule: ds_eq(champ_value(stable_id), constant(gi.label)))
end
defaut = @procedure.defaut_groupe_instructeur
if !tdc_options.include?(defaut.label)
new_defaut = @procedure.reload.groupe_instructeurs_but_defaut.first
@procedure.update!(defaut_groupe_instructeur: new_defaut)
reaffecter_all_dossiers_to_defaut_groupe
defaut.instructeurs.each { new_defaut.add(_1) }
defaut.destroy!
end
flash.notice = 'Les groupes instructeurs ont été ajoutés'
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
end
def wizard
if params[:choice][:state] == 'routage_custom'
new_label = procedure.defaut_groupe_instructeur.label + ' bis'
procedure.groupe_instructeurs
.create({ label: new_label, instructeurs: [current_administrateur.instructeur] })
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
elsif params[:choice][:state] == 'routage_simple'
redirect_to simple_routing_admin_procedure_groupe_instructeurs_path
end
end
def destroy_all_groups_but_defaut
reaffecter_all_dossiers_to_defaut_groupe
procedure.groupe_instructeurs_but_defaut.each(&:destroy!)
procedure.update!(routing_enabled: false, instructeurs_self_management_enabled: false)
procedure.defaut_groupe_instructeur.update!(
routing_rule: nil,
label: GroupeInstructeur::DEFAUT_LABEL,
closed: false
)
flash.notice = 'Tous les groupes instructeurs ont été supprimés'
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
end
def show
@procedure = procedure
@groupe_instructeur = groupe_instructeur
@ -48,10 +116,7 @@ module Administrateurs
def update
@groupe_instructeur = groupe_instructeur
if closed_params? && @groupe_instructeur.id == procedure.defaut_groupe_instructeur.id
redirect_to admin_procedure_groupe_instructeur_path(procedure, groupe_instructeur),
alert: "Il est impossible de désactiver le groupe dinstructeurs par défaut."
elsif @groupe_instructeur.update(groupe_instructeur_params)
if @groupe_instructeur.update(groupe_instructeur_params)
redirect_to admin_procedure_groupe_instructeur_path(procedure, groupe_instructeur),
notice: "Le nom est à présent « #{@groupe_instructeur.label} »."
else
@ -64,6 +129,26 @@ module Administrateurs
end
end
def update_state
@groupe_instructeur = procedure.groupe_instructeurs.find(params[:groupe_instructeur_id])
if closed_params? && @groupe_instructeur.id == procedure.defaut_groupe_instructeur.id
redirect_to admin_procedure_groupe_instructeur_path(procedure, @groupe_instructeur),
alert: "Il est impossible de désactiver le groupe dinstructeurs par défaut."
elsif @groupe_instructeur.update(closed: params[:closed])
state_for_notice = @groupe_instructeur.closed ? 'désactivé' : 'activé'
redirect_to admin_procedure_groupe_instructeur_path(procedure, @groupe_instructeur),
notice: "Le groupe #{@groupe_instructeur.label} est #{state_for_notice}."
else
@procedure = procedure
@instructeurs = paginated_instructeurs
@available_instructeur_emails = available_instructeur_emails
flash.now[:alert] = @groupe_instructeur.errors.full_messages
render :show
end
end
def destroy
@groupe_instructeur = groupe_instructeur
@ -76,7 +161,15 @@ module Administrateurs
else
@groupe_instructeur.destroy!
if procedure.groupe_instructeurs.active.one?
procedure.update!(routing_enabled: false)
procedure.update!(
routing_enabled: false,
instructeurs_self_management_enabled: false
)
procedure.defaut_groupe_instructeur.update!(
routing_rule: nil,
label: GroupeInstructeur::DEFAUT_LABEL,
closed: false
)
routing_notice = " et le routage a été désactivé"
end
flash[:notice] = "le groupe « #{@groupe_instructeur.label} » a été supprimé#{routing_notice}."
@ -112,6 +205,14 @@ module Administrateurs
redirect_to admin_procedure_groupe_instructeurs_path(procedure)
end
def reaffecter_all_dossiers_to_defaut_groupe
procedure.groupe_instructeurs_but_defaut.each do |gi|
gi.dossiers.find_each do |dossier|
dossier.assign_to_groupe_instructeur(procedure.defaut_groupe_instructeur, current_administrateur)
end
end
end
def add_instructeur
emails = params['emails'].presence || [].to_json
emails = JSON.parse(emails).map { EmailSanitizableConcern::EmailSanitizer.sanitize(_1) }
@ -193,7 +294,7 @@ module Administrateurs
def update_instructeurs_self_management_enabled
procedure.update!(instructeurs_self_management_enabled_params)
redirect_to admin_procedure_groupe_instructeurs_path(procedure),
redirect_to options_admin_procedure_groupe_instructeurs_path(procedure),
notice: "Lautogestion des instructeurs est #{procedure.instructeurs_self_management_enabled? ? "activée" : "désactivée"}."
end
@ -264,7 +365,7 @@ module Administrateurs
private
def closed_params?
groupe_instructeur_params[:closed] == "1"
params[:closed] == "1"
end
def procedure
@ -287,12 +388,21 @@ module Administrateurs
end
def groupe_instructeur_params
params.require(:groupe_instructeur).permit(:label, :closed)
params.require(:groupe_instructeur).permit(:label)
end
def paginated_groupe_instructeurs
procedure
.groupe_instructeurs
groupes = if params[:q].present?
query = ActiveRecord::Base.sanitize_sql_like(params[:q])
procedure
.groupe_instructeurs
.where('unaccent(label) ILIKE unaccent(?)', "%#{query}%")
else
procedure.groupe_instructeurs
end
groupes
.page(params[:page])
.per(ITEMS_PER_PAGE)
end

View file

@ -179,7 +179,6 @@ module Users
errors = submit_dossier_and_compute_errors
if errors.blank?
RoutingEngine.compute(@dossier)
@dossier.passer_en_construction!
@dossier.process_declarative!
NotificationMailer.send_en_construction_notification(@dossier).deliver_later
@ -538,9 +537,7 @@ module Users
@dossier.assign_to_groupe_instructeur(defaut_groupe_instructeur)
end
if !@dossier.procedure.feature_enabled?(:routing_rules) && @dossier.groupe_instructeur.nil?
errors += format_errors(errors: ["Le champ « #{@dossier.procedure.routing_criteria_name} » doit être rempli"])
end
RoutingEngine.compute(@dossier)
errors
end

View file

@ -26,6 +26,16 @@ module Mutations
.build(label: groupe_instructeur.label, closed: groupe_instructeur.closed, instructeurs: [current_administrateur.instructeur].compact)
if groupe_instructeur.save
# ugly hack to keep retro compatibility
# do not judge
if !ENV['OLD_GROUPE_INSTRUCTEURS_CREATE_API_PROCEDURE_ID'].nil? && demarche_number.in?(ENV['OLD_GROUPE_INSTRUCTEURS_CREATE_API_PROCEDURE_ID']&.split(',')&.map(&:to_i))
stable_id = procedure.groupe_instructeurs.first.routing_rule.left.stable_id
tdc = procedure.published_revision.types_de_champ.find_by(stable_id: stable_ids)
tdc.update(options: tdc.options['drop_down_options'].push(groupe_instructeur.label))
groupe_instructeur.update(routing_rule: ds_eq(champ_value(stable_id), constant(groupe_instruteur.label)))
end
result = { groupe_instructeur: }
if emails.present? || ids.present?

View file

@ -0,0 +1,14 @@
import { Controller } from '@hotwired/stimulus';
export class RadioEnabledSubmitController extends Controller {
static targets = ['submit'];
declare readonly submitTarget: HTMLButtonElement;
click() {
if (
this.element.querySelectorAll('input[type="radio"]:checked').length > 0
) {
this.submitTarget.disabled = false;
}
}
}

View file

@ -86,10 +86,22 @@ class GroupeInstructeur < ApplicationRecord
dossiers.empty? && (procedure.groupe_instructeurs.active.many? || (procedure.groupe_instructeurs.active.one? && closed))
end
def routing_to_configure?
rule = routing_rule
return true if !(rule.is_a?(Logic::Eq) && rule.left.is_a?(Logic::ChampValue) && rule.right.is_a?(Logic::Constant))
!routing_rule_matches_tdc?
end
private
def routing_rule_matches_tdc?
routing_tdc = procedure.active_revision.types_de_champ.find_by(stable_id: routing_rule.left.stable_id)
routing_rule.right.value.in?(routing_tdc.options['drop_down_options'])
end
def toggle_routing
procedure.update!(routing_enabled: procedure.groupe_instructeurs.active.many?)
procedure.update!(instructeurs_self_management_enabled: true) if procedure.routing_enabled?
end
serialize :routing_rule, LogicSerializer

View file

@ -728,16 +728,16 @@ class Procedure < ApplicationRecord
revisions.size - 2
end
def instructeurs_self_management?
routing_enabled? || instructeurs_self_management_enabled?
end
def defaut_groupe_instructeur_for_new_dossier
if !routing_enabled? || feature_enabled?(:procedure_routage_api)
defaut_groupe_instructeur
end
end
def groupe_instructeurs_but_defaut
groupe_instructeurs - [defaut_groupe_instructeur]
end
def can_be_deleted_by_administrateur?
brouillon? || dossiers.state_en_instruction.empty?
end

View file

@ -236,6 +236,10 @@ class ProcedureRevision < ApplicationRecord
[coordinate, coordinate&.type_de_champ]
end
def routable_types_de_champ
types_de_champ_public.filter { |tdc| [:drop_down_list].include?(tdc.type_champ.to_sym) }
end
private
def compute_estimated_fill_duration

View file

@ -1,4 +1,5 @@
class ProcedureRevisionChange
attr_reader :type_de_champ
def initialize(type_de_champ)
@type_de_champ = type_de_champ
end

View file

@ -2,7 +2,7 @@ module RoutingEngine
def self.compute(dossier)
return if !dossier.procedure.feature_enabled?(:routing_rules)
matching_groupe = dossier.procedure.groupe_instructeurs.active.find do |gi|
matching_groupe = dossier.procedure.groupe_instructeurs.active.reject(&:routing_to_configure?).find do |gi|
gi.routing_rule&.compute(dossier.champs)
end
matching_groupe ||= dossier.procedure.defaut_groupe_instructeur

View file

@ -131,6 +131,7 @@ class TypeDeChamp < ApplicationRecord
has_one :procedure, through: :revision
delegate :estimated_fill_duration, :estimated_read_duration, :tags_for_template, :libelle_for_export, to: :dynamic_type
delegate :used_by_routing_rules?, to: :revision_type_de_champ
class WithIndifferentAccess
def self.load(options)

View file

@ -1,67 +0,0 @@
- if groupes_instructeurs.many? && !procedure.feature_enabled?(:routing_rules)
.card
= form_for procedure,
url: { action: :update_routing_criteria_name },
html: { class: 'form' } do |f|
= f.label :routing_criteria_name do
= t('.routing.title')
%p.notice
= f.text_field :routing_criteria_name, required: true
= f.submit t('.button.rename'), class: 'button primary send'
.card
%h2.card-title= t('.group_management.title')
= form_for :groupe_instructeur, html: { class: 'form' } do |f|
= f.label :label do
= t('.add_a_group.title')
- if groupes_instructeurs.many?
%p.notice
= t('.add_a_group.notice', routing_criteria_name: procedure.routing_criteria_name)
= f.text_field :label, required: true
= f.submit t('.button.add_group'), class: "button primary send"
- csv_max_size = Administrateurs::GroupeInstructeursController::CSV_MAX_SIZE
- if procedure.publiee_or_close?
= form_tag import_admin_procedure_groupe_instructeurs_path(procedure), method: :post, multipart: true, class: "mt-4 form" do
= label_tag t('.csv_import.title')
%p.notice
= t('.csv_import.notice_1_html',
instructeurs_link: link_to(t('.csv_import.link_text'), t('.csv_import.instructeurs_file_path')),
groupes_link: link_to(t('.csv_import.link_text'), t('.csv_import.groupes_file_path')))
%p.notice
= t('.csv_import.notice_2', csv_max_size: number_to_human_size(csv_max_size))
= file_field_tag :csv_file, required: true, accept: 'text/csv', size: "1"
= submit_tag t('.csv_import.import_file'), class: 'button primary send', data: { disable_with: "Envoi...", confirm: t('.csv_import.import_file_alert') }
- else
%p.mt-4.form.font-weight-bold.mb-2.text-lg
= t('.csv_import.title')
%p.notice
= t('.csv_import.import_file_procedure_not_published')
- if procedure.groupe_instructeurs.many?
%table.table.mt-2
%thead
%tr
// i18n-tasks-use t('.existing_groupe')
%th{ colspan: 2 }= t(".existing_groupe", count: groupes_instructeurs.total_count)
%th.actions
= link_to "Exporter au format CSV", export_groupe_instructeurs_admin_procedure_groupe_instructeurs_path(procedure, format: :csv)
%tbody
- groupes_instructeurs.each do |group|
%tr
%td= group.label
%td.setup= link_to t('.set_up'), admin_procedure_groupe_instructeur_path(procedure, group)
- if group.can_delete?
%td.actions
= link_to admin_procedure_groupe_instructeur_path(procedure, group), { method: :delete, class: 'button', data: { confirm: t('.group_management.delete_confirmation', group_name: group.label) }} do
%span.icon.delete
= t('.group_management.delete')
- else
%td.actions
= link_to reaffecter_dossiers_admin_procedure_groupe_instructeur_path(procedure, group), class: 'button', title: t('.group_management.move_files_confirmation') do
%span.icon.follow
= t('.group_management.move_files', count: group.dossiers.visible_by_administration.size)
= paginate groupes_instructeurs, views_prefix: 'shared'

View file

@ -0,0 +1,28 @@
- key = @procedure.groupe_instructeurs.one? ? 'instructeurs' : 'groupes'
%section.fr-accordion.fr-mb-3w
%h3.fr-accordion__title
%button.fr-accordion__btn{ "aria-controls" => "accordion-106", "aria-expanded" => "false" }
= t(".csv_import.#{key}.title")
.fr-collapse#accordion-106
- csv_max_size = Administrateurs::GroupeInstructeursController::CSV_MAX_SIZE
- if procedure.publiee_or_close?
%p.notice
= t(".csv_import.#{key}.notice_1_html", csv_max_size: number_to_human_size(csv_max_size))
%p.notice
= t(".csv_import.#{key}.notice_2")
= form_tag import_admin_procedure_groupe_instructeurs_path(@procedure), method: :post, multipart: true, class: "mt-4 form flex justify-between align-center" do
= file_field_tag :csv_file, required: true, accept: 'text/csv', size: "1"
= submit_tag t('.csv_import.import_file'), class: 'fr-btn fr-btn--secondary', data: { disable_with: "Envoi...", confirm: t('.csv_import.import_file_alert') }
- else
%p.mt-4.form.font-weight-bold.mb-2.text-lg
= t(".csv_import.#{key}.title")
%p.notice
= t('.csv_import.import_file_procedure_not_published')
- if groupe_instructeurs.many?
.flex.justify-between.align-center.mt-4
%div
= t(".existing_groupe", count: groupe_instructeurs.total_count)
= button_to "Exporter au format CSV",
export_groupe_instructeurs_admin_procedure_groupe_instructeurs_path(@procedure, format: :csv),
method: :get,
class: 'fr-btn fr-btn--secondary'

View file

@ -1,14 +0,0 @@
.card
%h2.card-title Lautogestion des instructeurs
%p.notice= t('.self_managment_notice_html')
= form_for procedure,
method: :patch,
url: update_instructeurs_self_management_enabled_admin_procedure_groupe_instructeurs_path(procedure),
data: { controller: 'autosubmit', turbo: 'true' },
html: { class: 'form procedure-form__column--form no-background' } do |f|
%label.toggle-switch
= f.check_box :instructeurs_self_management_enabled, class: 'toggle-switch-checkbox'
%span.toggle-switch-control.round
%span.toggle-switch-label.on
%span.toggle-switch-label.off

View file

@ -1,4 +0,0 @@
.card
%h2.card-title= t('.title')
%p.notice= t('.notice_html')

View file

@ -0,0 +1,8 @@
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[t('.procedures'), admin_procedures_path],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
[(@procedure.groupe_instructeurs.many? ? 'Groupes' : 'Instructeurs'), admin_procedure_groupe_instructeurs_path(@procedure)],
['Ajout']] }
= render Procedure::InstructeursMenuComponent.new(procedure: @procedure) do
= render Procedure::GroupesAjoutComponent.new(procedure: @procedure, groupe_instructeurs: @groupes_instructeurs)

View file

@ -1,31 +1,14 @@
- if @procedure.routing_enabled?
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[t('.procedures'), admin_procedures_path],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
[t('.instructors_group')]] }
- else
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[t('.procedures'), admin_procedures_path],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
['Instructeurs']] }
.container.groupe-instructeur
%h1 Gérer les instructeurs et les options d'instruction de « #{@procedure.libelle} »
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[t('.procedures'), admin_procedures_path],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
[@procedure.groupe_instructeurs.many? ? 'Groupes' : 'Instructeurs']] }
= render Procedure::InstructeursMenuComponent.new(procedure: @procedure) do
- if @procedure.groupe_instructeurs.one?
= render partial: 'administrateurs/groupe_instructeurs/instructeurs',
locals: { procedure: @procedure,
groupe_instructeur: @procedure.defaut_groupe_instructeur,
instructeurs: @instructeurs,
available_instructeur_emails: @available_instructeur_emails,
disabled_as_super_admin: administrateur_as_manager? }
- if !@procedure.routing_enabled?
= render partial: 'administrateurs/groupe_instructeurs/instructeurs_self_management', locals: { procedure: @procedure }
= render partial: 'administrateurs/groupe_instructeurs/routing', locals: { procedure: @procedure }
= render partial: 'administrateurs/groupe_instructeurs/edit', locals: { procedure: @procedure, groupes_instructeurs: @groupes_instructeurs }
- if @procedure.routing_enabled? && @procedure.feature_enabled?(:routing_rules)
= render(Procedure::RoutingRulesComponent.new(revision: @procedure.active_revision,
groupe_instructeurs: @procedure.groupe_instructeurs))
= render Procedure::InstructeursManagementComponent.new(procedure: @procedure,
groupe_instructeur: @procedure.groupe_instructeurs.first,
instructeurs: @instructeurs,
available_instructeur_emails: @available_instructeur_emails,
disabled_as_super_admin: administrateur_as_manager?)
- else
= render Procedure::GroupesManagementComponent.new(procedure: @procedure, groupe_instructeurs: @groupes_instructeurs, query: params[:q])

View file

@ -0,0 +1,8 @@
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[t('.procedures'), admin_procedures_path],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
[(@procedure.groupe_instructeurs.many? ? 'Groupes' : 'Instructeurs'), admin_procedure_groupe_instructeurs_path(@procedure)],
['Options']] }
= render Procedure::InstructeursMenuComponent.new(procedure: @procedure) do
= render Procedure::InstructeursOptionsComponent.new(procedure: @procedure, state: params[:state])

View file

@ -4,8 +4,9 @@
['Groupes dinstructeurs', admin_procedure_groupe_instructeurs_path(@procedure)],
[@groupe_instructeur.label]] }
.container.groupe-instructeur
= render partial: 'administrateurs/groups_header'
= render Procedure::InstructeursMenuComponent.new(procedure: @procedure) do
= render Procedure::OneGroupeManagementComponent.new(revision: @procedure.active_revision, groupe_instructeur: @groupe_instructeur)
= render partial: 'administrateurs/groupe_instructeurs/instructeurs',
locals: { procedure: @procedure,
groupe_instructeur: @groupe_instructeur,

View file

@ -0,0 +1,27 @@
= render partial: 'administrateurs/breadcrumbs',
locals: { steps: [[t('.procedures'), admin_procedures_path],
[@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)],
['Groupes', admin_procedure_groupe_instructeurs_path(@procedure)],
['Routage à partir dun champ']] }
= render Procedure::InstructeursMenuComponent.new(procedure: @procedure) do
- content_for(:title, 'Routage')
%h1 Routage à partir dun champ
= form_for :create_simple_routing,
method: :post,
data: { controller: 'radio-enabled-submit' },
url: create_simple_routing_admin_procedure_groupe_instructeurs_path(@procedure) do |f|
%div{ data: { 'action': "click->radio-enabled-submit#click" } }
.notice
Sélectionner le champ à partir duquel créer des groupes d'instructeurs
- buttons_content = @procedure.active_revision.routable_types_de_champ.map { |tdc| { label: tdc.libelle, value: tdc.stable_id } }
= render Dsfr::RadioButtonListComponent.new(form: f,
target: :stable_id,
buttons: buttons_content)
%ul.fr-btns-group.fr-btns-group--inline-sm
%li
= link_to 'Retour', options_admin_procedure_groupe_instructeurs_path(@procedure, state: :choix), class: 'fr-btn fr-btn--secondary'
%li
%button.fr-btn{ disabled: true, data: { 'radio-enabled-submit-target': 'submit' } } Créer les groupes

View file

@ -1,2 +1,3 @@
= turbo_stream.replace 'routing-rules', render(Procedure::RoutingRulesComponent.new(revision: @procedure.active_revision,
groupe_instructeurs: @procedure.groupe_instructeurs))
= turbo_stream.replace dom_id(@groupe_instructeur, :routing),
render(Procedure::OneGroupeManagementComponent.new(revision: @procedure.active_revision,
groupe_instructeur: @groupe_instructeur))

View file

@ -7,7 +7,7 @@
|
= link_to t('instructeurs.dossiers.header.banner.statistics'), stats_instructeur_procedure_path(procedure), class: 'header-link'
- if procedure.instructeurs_self_management?
- if procedure.instructeurs_self_management_enabled?
|
- if can_manage_groupe_instructeurs?(procedure)
= link_to t('instructeurs.dossiers.header.banner.instructeurs'), admin_procedure_groupe_instructeurs_path(procedure), class: 'header-link'

View file

@ -8,7 +8,6 @@ en:
groupe_instructeurs:
index:
procedures: Procedures
instructors_group: Group of instructors
add_instructeur:
wrong_address:
one: "%{emails} is not a valid email address"
@ -24,48 +23,19 @@ en:
assigned_instructeur:
one: "%{count} instructor is assigned"
other: "%{count} instructors are assigned"
edit:
routing:
title: Label of the groups list
group_management:
title: Group management
delete: delete the group
delete_confirmation: Are you sure you want to delete the group "%{group_name}"
move_files:
zero: move draft files
one: move one file
other: move the %{count} files
move_files_confirmation: Reassign folders to another group so you can delete it
add_a_group:
title: Add a group
notice: This group will be a choice from the list "%{routing_criteria_name}"
import_export:
csv_import:
title: CSV Import
notice_1_html: The csv file must have 1 column with instructors emails(%{instructeurs_link}). For routed procedures, the csv file must have 2 columns (Group, Email) and be separated by commas (%{groupes_link}). The import does not overwrite existing groups and instructors.
notice_2: The size of the file must be less than %{csv_max_size}.
import_file: Import file
import_file_procedure_not_published: The import of instructors by CSV file is available once the process has been published
import_file_alert: Instructors added to the procedure will receive an email. Are you sure you want to continue ?"
link_text: file example
instructeurs_file_path: /csv/import-instructeurs-test.csv
groupes_file_path: /csv/en/import-groupe-test.csv
set_up: set up
button:
add_group: Add group
rename: Rename
import_file: Importer le fichier
import_file_alert: Tous les instructeurs ajoutés à la procédure vont être notifiés par email. Voulez-vous continuer ?
import_file_procedure_not_published: Limport par fichier CSV est disponible une fois la démarche publiée
groupes:
title: Import / Export en masse
notice_1_html: Pour l'import, votre fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules (<a href=/csv/fr/import-groupe-test.csv>exemple de fichier</a>). Le poids du fichier doit être inférieur %{csv_max_size}.
notice_2: Limport nécrase pas les groupes existants. Il permet uniquement d'en ajouter. Pour supprimer un groupe, allez dans la page dédiée et cliquez sur le bouton « Supprimer ».
instructeurs:
title: Import en masse
notice_1_html: Pour l'import, le fichier csv doit comporter 1 seule colonne (Email) avec une adresse email d'instructeur par ligne (<a href=/csv/import-instructeurs-test.csv>exemple de fichier</a>). Le poids du fichier doit être inférieur %{csv_max_size}.
notice_2: Limport nécrase pas les instructeurs existants. Il permet uniquement d'en ajouter. Pour supprimer un instructeur, cliquez sur le bouton « Retirer ».
existing_groupe:
one: "%{count} group exist"
other: "%{count} groups exist"
routing:
title: Routing
notice_html: |
Routing is a feature for procedures requiring the sharing of instructions between different groups according to a specific criterion (territory, theme or other).
<br><br>
This feature makes it possible to route the files to each group, and to no longer need to filter its files among a large quantity of requests. It is therefore particularly suitable for national approaches instructed locally.
<br><br>
Instructors only see the files that concern them, and therefore do not have access to data outside their scope.
<br><br>
Routing is activated once there are at least two active instructors groups
instructeurs_self_management:
self_managment_notice_html: |
Instructor Self-Management allows instructors to self-manage the list of Gait Instructors.
one: "%{count} groupe existe"
other: "%{count} groupes existent"

View file

@ -30,6 +30,34 @@ fr:
assigned_instructeur:
one: "%{count} instructeur est affecté"
other: "%{count} instructeurs sont affectés"
import_export:
csv_import:
import_file: Importer le fichier
import_file_alert: Tous les instructeurs ajoutés à la procédure vont être notifiés par email. Voulez-vous continuer ?
import_file_procedure_not_published: Limport par fichier CSV est disponible une fois la démarche publiée
groupes:
title: Import / Export en masse
notice_1_html: Pour l'import, votre fichier csv doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules (<a href=/csv/fr/import-groupe-test.csv>exemple de fichier</a>). Le poids du fichier doit être inférieur %{csv_max_size}.
notice_2: Limport nécrase pas les groupes existants. Il permet uniquement d'en ajouter. Pour supprimer un groupe, allez dans la page dédiée et cliquez sur le bouton « Supprimer ».
instructeurs:
title: Import en masse
notice_1_html: Pour l'import, le fichier csv doit comporter 1 seule colonne (Email) avec une adresse email d'instructeur par ligne (<a href=/csv/import-instructeurs-test.csv>exemple de fichier</a>). Le poids du fichier doit être inférieur %{csv_max_size}.
notice_2: Limport nécrase pas les instructeurs existants. Il permet uniquement d'en ajouter. Pour supprimer un instructeur, cliquez sur le bouton « Retirer ».
existing_groupe:
one: "%{count} groupe existe"
other: "%{count} groupes existent"
groupe:
one: "%{count} groupe"
other: "%{count} groupes"
found:
one: "trouvé"
other: "trouvés"
ajout:
procedures: Démarches
options:
procedures: Démarches
simple_routing:
procedures: Démarches
edit:
routing:
title: Libellé de la liste de groupes
@ -46,14 +74,6 @@ fr:
title: Ajouter un nom de groupe
notice: Ce groupe sera un choix de la liste "%{routing_criteria_name}"
csv_import:
title: Importer par CSV
notice_1_html: |
Pour une procédure standard, le fichier csv doit comporter 1 seule colonne (Email) avec une adresse email d'instructeur par ligne (%{instructeurs_link}).
Pour une procédure routée, le fichier doit comporter 2 colonnes (Groupe, Email) et être séparé par des virgules (%{groupes_link}).
notice_2: Limport nécrase pas les instructeurs existants. Il permet uniquement d'en ajouter. Pour supprimer un instructeur, cliquez sur le bouton « retirer ». Le poids du fichier doit être inférieur %{csv_max_size}.
import_file: Importer le fichier
import_file_procedure_not_published: Limport dinstructeurs par fichier CSV est disponible une fois la démarche publiée
import_file_alert: Tous les instructeurs ajoutés à la procédure vont être informés par email. Voulez-vous continuer ?
link_text: exemple de fichier
instructeurs_file_path: /csv/import-instructeurs-test.csv
groupes_file_path: /csv/fr/import-groupe-test.csv
@ -72,6 +92,3 @@ fr:
button:
self_managment_toggle: Activer lautogestion des instructeurs
add_group: Ajouter le groupe
instructeurs_self_management:
self_managment_notice_html: |
Lautogestion des instructeurs permet aux instructeurs de gérer eux-mêmes la liste des instructeurs de la démarche.

View file

@ -522,6 +522,8 @@ Rails.application.routes.draw do
resources :mail_templates, only: [:edit, :update, :show]
resources :groupe_instructeurs, only: [:index, :show, :create, :update, :destroy] do
patch 'update_state' => 'groupe_instructeurs#update_state'
member do
post 'add_instructeur'
delete 'remove_instructeur'
@ -530,6 +532,13 @@ Rails.application.routes.draw do
end
collection do
get 'options'
get 'ajout'
post 'ajout' => 'groupe_instructeurs#create'
patch 'wizard'
get 'simple_routing'
post 'create_simple_routing'
delete 'destroy_all_groups_but_defaut'
patch 'update_routing_criteria_name'
patch 'update_instructeurs_self_management_enabled'
post 'import'

View file

@ -1,4 +1,4 @@
describe Procedure::RoutingRulesComponent, type: :component do
describe Procedure::OneGroupeManagementComponent, type: :component do
include Logic
describe 'render' do
@ -9,16 +9,7 @@ describe Procedure::RoutingRulesComponent, type: :component do
subject do
render_inline(described_class.new(revision: procedure.active_revision,
groupe_instructeurs: procedure.groupe_instructeurs))
end
context 'when there are no types de champ that can be routed' do
before do
procedure.publish_revision!
procedure.reload
subject
end
it { expect(page).to have_text('Ajoutez ce champ dans la page') }
groupe_instructeur: procedure.defaut_groupe_instructeur))
end
context 'when there are types de champ that can be routed' do
@ -32,7 +23,7 @@ describe Procedure::RoutingRulesComponent, type: :component do
procedure.reload
subject
end
it { expect(page).to have_text('Router vers') }
it { expect(page).to have_text('à configurer') }
end
end
end

View file

@ -1,8 +1,10 @@
describe Administrateurs::GroupeInstructeursController, type: :controller do
render_views
include Logic
let(:admin) { create(:administrateur) }
let(:procedure) { create(:procedure, :published, :for_individual, administrateurs: [admin]) }
let!(:gi_1_1) { procedure.defaut_groupe_instructeur }
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
@ -13,13 +15,25 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
describe '#index' do
context 'of a procedure I own' do
before { get :index, params: { procedure_id: procedure.id } }
before { get :index, params: }
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) }
let(:params) { { procedure_id: procedure.id } }
it do
expect(response).to have_http_status(:ok)
expect(response.body).to include(gi_1_1.label)
expect(response.body).to include(gi_1_2.label)
expect(response.body).not_to include(gi_2_2.label)
end
context 'when there is a search' do
let(:params) { { procedure_id: procedure.id, q: '2' } }
it do
expect(assigns(:groupes_instructeurs)).to match_array([gi_1_2])
end
end
end
end
end
@ -46,6 +60,45 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
expect(assigns(:available_instructeur_emails)).to match_array(['instructeur_3@ministere-a.gouv.fr', 'instructeur_4@ministere-b.gouv.fr'])
end
end
context 'group without routing rule' do
before { get :show, params: { procedure_id: procedure.id, id: gi_1_1.id } }
it do
expect(response).to have_http_status(:ok)
expect(response.body).to include('à configurer')
end
end
context 'group with routing rule matching tdc' do
let!(:drop_down_tdc) { create(:type_de_champ_drop_down_list, procedure: procedure, drop_down_options: options) }
let(:options) { procedure.groupe_instructeurs.pluck(:label) }
before do
gi_1_1.update(routing_rule: ds_eq(champ_value(drop_down_tdc.stable_id), constant(gi_1_1.label)))
get :show, params: { procedure_id: procedure.id, id: gi_1_1.id }
end
it do
expect(response).to have_http_status(:ok)
expect(response.body).not_to include('à configurer')
end
end
context 'group with routing rule not matching tdc' do
let!(:drop_down_tdc) { create(:type_de_champ_drop_down_list, procedure: procedure, drop_down_options: options) }
let(:options) { ['parmesan', 'brie', 'morbier'] }
before do
gi_1_1.update(routing_rule: ds_eq(champ_value(drop_down_tdc.stable_id), constant(gi_1_1.label)))
get :show, params: { procedure_id: procedure.id, id: gi_1_1.id }
end
it do
expect(response).to have_http_status(:ok)
expect(response.body).to include('à configurer')
end
end
end
describe '#create' do
@ -181,7 +234,6 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
describe '#update' do
let(:new_name) { 'nouveau nom du groupe' }
let(:closed_value) { '0' }
let!(:procedure_non_routee) { create(:procedure, :published, :for_individual, administrateurs: [admin]) }
let!(:gi_1_1) { procedure_non_routee.defaut_groupe_instructeur }
@ -190,7 +242,7 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
params: {
procedure_id: procedure_non_routee.id,
id: gi_1_1.id,
groupe_instructeur: { label: new_name, closed: closed_value }
groupe_instructeur: { label: new_name }
}
gi_1_1.reload
end
@ -202,18 +254,6 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
expect(flash.notice).to be_present
end
context 'when we try do disable the default groupe instructeur' do
let(:closed_value) { '1' }
let!(:gi_1_2) { procedure.groupe_instructeurs.create(label: 'groupe instructeur 2') }
it do
expect(subject).to redirect_to admin_procedure_groupe_instructeur_path(procedure_non_routee, gi_1_1)
expect(gi_1_1.label).not_to eq(new_name)
expect(gi_1_1.closed).to eq(false)
expect(flash.alert).to eq('Il est impossible de désactiver le groupe dinstructeurs par défaut.')
end
end
context 'when the name is already taken' do
let!(:gi_1_2) { procedure_non_routee.groupe_instructeurs.create(label: 'groupe instructeur 2') }
let(:new_name) { gi_1_2.label }
@ -225,6 +265,45 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
end
end
describe '#update_state' do
let(:closed_value) { '0' }
let!(:procedure_non_routee) { create(:procedure, :published, :for_individual, administrateurs: [admin]) }
let!(:gi_1_1) { procedure_non_routee.defaut_groupe_instructeur }
let!(:gi_1_2) { procedure_non_routee.groupe_instructeurs.create(label: 'groupe instructeur 2') }
before do
patch :update_state,
params: {
procedure_id: procedure_non_routee.id,
groupe_instructeur_id: group.id,
closed: closed_value
}
group.reload
end
context 'when we try do disable the default groupe instructeur' do
let(:closed_value) { '1' }
let(:group) { gi_1_1 }
it do
expect(subject).to redirect_to admin_procedure_groupe_instructeur_path(procedure_non_routee, gi_1_1)
expect(gi_1_1.closed).to eq(false)
expect(flash.alert).to eq('Il est impossible de désactiver le groupe dinstructeurs par défaut.')
end
end
context 'when we try do disable the second groupe instructeur' do
let(:closed_value) { '1' }
let(:group) { gi_1_2 }
it do
expect(subject).to redirect_to admin_procedure_groupe_instructeur_path(procedure_non_routee, gi_1_2)
expect(gi_1_2.closed).to eq(true)
expect(flash.notice).to eq('Le groupe groupe instructeur 2 est désactivé.')
end
end
end
describe '#add_instructeur_procedure_non_routee' do
# faire la meme chose sur une procedure non routee
let(:procedure_non_routee) { create :procedure }
@ -624,4 +703,26 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
it { expect(procedure.reload.routing_criteria_name).to eq('new name !') }
end
describe '#create_simple_routing' do
let!(:procedure3) do
create(:procedure,
types_de_champ_public: [
{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] },
{ type: :text, libelle: 'Un champ texte' }
],
administrateurs: [admin])
end
let!(:drop_down_tdc) { procedure3.draft_revision.types_de_champ.first }
before { post :create_simple_routing, params: { procedure_id: procedure3.id, create_simple_routing: { stable_id: drop_down_tdc.stable_id } } }
it do
expect(response).to redirect_to(admin_procedure_groupe_instructeurs_path(procedure3))
expect(flash.notice).to eq 'Les groupes instructeurs ont été ajoutés'
expect(procedure3.groupe_instructeurs.pluck(:label)).to match_array(['Paris', 'Lyon', 'Marseille'])
expect(procedure3.reload.defaut_groupe_instructeur.routing_rule).to eq(ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
end
end
end

View file

@ -412,16 +412,6 @@ describe Users::DossiersController, type: :controller do
it { expect(flash.alert).to eq(["Le champ « l » doit être rempli, #{anchor_to_first_champ}"]) }
end
context 'when a dossier is invalid' do
before do
allow_any_instance_of(Dossier).to receive(:groupe_instructeur).and_return(double(nil?: true))
subject
end
it { expect(response).to render_template(:brouillon) }
it { expect(flash.alert).to eq(["Le champ « Votre ville » doit être rempli"]) }
end
context 'when dossier has no champ' do
let(:submit_payload) { { id: dossier.id } }

View file

@ -5,12 +5,15 @@ describe RoutingEngine, type: :model do
describe '.compute' do
let(:procedure) do
create(:procedure).tap do |p|
p.groupe_instructeurs.create(label: 'a second group')
p.groupe_instructeurs.create(label: 'a third group')
end
create(:procedure,
types_de_champ_public: [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }]).tap do |p|
p.groupe_instructeurs.create(label: 'a second group')
p.groupe_instructeurs.create(label: 'a third group')
end
end
let(:drop_down_tdc) { procedure.draft_revision.types_de_champ.first }
let(:dossier) { create(:dossier, procedure:) }
let(:defaut_groupe) { procedure.defaut_groupe_instructeur }
let(:gi_2) { procedure.groupe_instructeurs.find_by(label: 'a second group') }
@ -34,13 +37,26 @@ describe RoutingEngine, type: :model do
it { is_expected.to eq(defaut_groupe) }
end
context 'with a matching rules' do
before { gi_2.update(routing_rule: constant(true)) }
context 'with rules not configured yet' do
before do
procedure.groupe_instructeurs.each do |gi|
gi.update(routing_rule: ds_eq(empty, empty))
end
end
it { is_expected.to eq(defaut_groupe) }
end
context 'with a matching rule' do
before do
gi_2.update(routing_rule: ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
dossier.champs.first.update(value: 'Lyon')
end
it { is_expected.to eq(gi_2) }
end
context 'with a closed gi with a matching rules' do
context 'with a closed gi with a matching rule' do
before { gi_2.update(routing_rule: constant(true), closed: true) }
it { is_expected.to eq(defaut_groupe) }

View file

@ -17,24 +17,7 @@ describe 'Manage procedure instructeurs', js: true do
scenario 'it works' do
visit admin_procedure_path(procedure)
find('#groupe-instructeurs').click
expect(page).to have_css("h1", text: "Gérer les instructeurs et les options d'instruction de « #{procedure.libelle} »")
end
end
context 'as admin not from manager' do
let(:manager) { false }
scenario 'can add instructeur' do
visit admin_procedure_groupe_instructeurs_path(procedure)
expect {
fill_in "instructeur_emails", with: create(:instructeur).email
click_on "Affecter"
}.to change { procedure.instructeurs.count }.by(1)
expect {
fill_in "groupe_instructeur_label", with: "Bordeaux"
click_on "Ajouter le groupe"
}.to change { procedure.groupe_instructeurs.count }.by(1)
expect(procedure.reload.routing_enabled).to eq true
expect(page).to have_css("h1", text: "Gestion des instructeurs")
end
end

View file

@ -1,48 +0,0 @@
describe 'As an administrateur I can manage procedure routing', js: true do
include Logic
let(:administrateur) { procedure.administrateurs.first }
let!(:gi_1) { procedure.defaut_groupe_instructeur }
let!(:gi_2) { procedure.groupe_instructeurs.create(label: 'a second group') }
let!(:gi_3) { procedure.groupe_instructeurs.create(label: 'a third group') }
let(:procedure) do
create(:procedure).tap do |p|
p.draft_revision.add_type_de_champ(
type_champ: :drop_down_list,
libelle: 'Un champ choix simple',
options: { "drop_down_other" => "0", "drop_down_options" => ["", "Premier choix", "Deuxième choix", "Troisième choix"] }
)
end
end
let(:drop_down_tdc) { procedure.draft_revision.types_de_champ.first }
before do
Flipper.enable(:routing_rules, procedure)
procedure.publish_revision!
login_as administrateur.user, scope: :user
end
it 'routes from a drop_down_list' do
visit admin_procedure_groupe_instructeurs_path(procedure)
within('.condition-table tbody tr:nth-child(1)', match: :first) do
expect(page).to have_select('targeted_champ', options: ['Sélectionner', 'Un champ choix simple'])
within('.target') { select('Un champ choix simple') }
within('.value') { select('Premier choix') }
end
expected_routing_rule = ds_eq(champ_value(drop_down_tdc.stable_id), constant('Premier choix'))
wait_until { gi_2.reload.routing_rule == expected_routing_rule }
end
it 'displays groupes instructeurs by alphabetic order' do
visit admin_procedure_groupe_instructeurs_path(procedure)
within('.condition-table tbody tr:nth-child(1)', match: :first) do
expect(page).to have_content 'second group'
expect(page).not_to have_content 'défaut'
end
end
end

View file

@ -1,238 +0,0 @@
describe 'The routing', js: true do
let(:password) { 'a very complicated password' }
let(:procedure) { create(:procedure, :with_type_de_champ, :with_service, :for_individual, :with_zone) }
let(:administrateur) { create(:administrateur, procedures: [procedure]) }
let(:scientifique_user) { create(:user, password: password) }
let(:litteraire_user) { create(:user, password: password) }
before do
procedure.update(routing_enabled: true)
procedure.defaut_groupe_instructeur.instructeurs << administrateur.instructeur
end
scenario 'works' do
login_as administrateur.user, scope: :user
visit admin_procedure_path(procedure.id)
find('#groupe-instructeurs').click
# add littéraire groupe
fill_in 'Ajouter un nom de groupe', with: 'littéraire'
click_on 'Ajouter le groupe'
expect(page).to have_text('Le groupe dinstructeurs « littéraire » a été créé et le routage a été activé.')
# add victor to littéraire groupe
fill_in 'Emails', with: 'victor@inst.com'
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur victor@inst.com a été affecté")
victor = User.find_by(email: 'victor@inst.com').instructeur
# add superwoman to littéraire groupe
fill_in 'Emails', with: 'superwoman@inst.com'
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté")
superwoman = User.find_by(email: 'superwoman@inst.com').instructeur
# rename routing criteria to spécialité
click_on 'Groupes dinstructeurs'
fill_in 'Libellé de la liste de groupes', with: 'spécialité'
click_on 'Renommer'
expect(page).to have_text('Le libellé est maintenant « spécialité ».')
expect(page).to have_field('Libellé de la liste de groupes', with: 'spécialité')
# add inactive groupe
fill_in 'Ajouter un nom de groupe', with: 'non visible car inactif'
click_on 'Ajouter le groupe'
check "Groupe inactif"
click_on 'Modifier'
# add scientifique groupe
click_on 'Groupes dinstructeurs'
fill_in 'Ajouter un nom de groupe', with: 'scientifique'
click_on 'Ajouter le groupe'
expect(page).to have_text('Le groupe dinstructeurs « scientifique » a été créé.')
# add marie to scientifique groupe
fill_in 'Emails', with: 'marie@inst.com'
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur marie@inst.com a été affecté")
marie = User.find_by(email: 'marie@inst.com').instructeur
# add superwoman to scientifique groupe
fill_in 'Emails', with: 'superwoman@inst.com'
perform_enqueued_jobs { click_on 'Affecter' }
expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté")
# 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
find('.fr-search-bar .fr-btn').click
expect(page).to have_text('0 dossier trouvé')
fill_in 'q', with: litteraire_user.email
find('.fr-search-bar .fr-btn').click
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))
# follow the dossier
click_on 'Suivre le dossier'
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)
# follow the dossier
click_on scientifique_user.email
click_on 'Suivre le dossier'
log_out
# litteraire_user change its dossier
visit new_user_session_path
sign_in_with litteraire_user.email, password
click_on litteraire_user.dossiers.first.id.to_s
click_on 'Modifier mon dossier'
fill_in litteraire_user.dossiers.first.champs_public.first.libelle, with: 'some value'
wait_for_autosave(false)
click_on 'Déposer les modifications'
log_out
# the litteraires instructeurs should have a notification
visit new_user_session_path
sign_in_with victor.user.email, password
## on the procedures list
expect(page).to have_current_path(instructeur_procedures_path)
expect(find('.procedure-stats')).to have_css('span.notifications')
## on the dossiers list
click_on procedure.libelle
expect(page).to have_current_path(instructeur_procedure_path(procedure))
expect(find('.tabs')).to have_css('span.notifications')
## on the dossier itself
click_on 'suivi'
click_on litteraire_user.email
expect(page).to have_current_path(instructeur_dossier_path(procedure, litteraire_user.dossiers.first))
expect(page).to have_text('Annotations privées')
expect(find('.tabs')).to have_css('span.notifications')
log_out
# the scientifiques instructeurs should not have a notification
visit new_user_session_path
sign_in_with marie.user.email, password
expect(page).to have_current_path(instructeur_procedures_path)
expect(find('.procedure-stats')).not_to have_css('span.notifications')
log_out
# the instructeurs who belong to scientifique AND litteraire groups manage scientifique and litterraire dossiers
register_instructeur_and_log_in(superwoman.email)
visit instructeur_procedure_path(procedure, params: { statut: 'tous' })
expect(page).to have_text(litteraire_user.email)
expect(page).to have_text(scientifique_user.email)
# follow the dossier
click_on scientifique_user.email
click_on 'Suivre le dossier'
visit instructeur_procedure_path(procedure, params: { statut: 'tous' })
click_on litteraire_user.email
click_on 'Suivre le dossier'
log_out
# scientifique_user updates its group
user_update_group(scientifique_user, 'littéraire')
# the instructeurs who belong to scientifique AND litteraire groups should have a notification
visit new_user_session_path
sign_in_with superwoman.user.email, password
expect(page).to have_current_path(instructeur_procedures_path)
expect(find('.procedure-stats')).to have_css('span.notifications')
end
def publish_procedure(procedure)
click_on procedure.libelle
find('#publish-procedure-link').click
fill_in 'lien_site_web', with: 'http://some.website'
click_on 'Publier'
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'
choose 'Monsieur'
fill_in 'individual_nom', with: 'Nom'
fill_in 'individual_prenom', with: 'Prenom'
click_button('Continuer')
select(groupe, from: 'dossier_groupe_instructeur_id')
wait_for_autosave
click_on 'Déposer le dossier'
expect(page).to have_text('Merci')
log_out
end
def user_update_group(user, new_group)
login_as user, scope: :user
visit dossiers_path
click_on user.dossiers.first.id.to_s
click_on "Modifier mon dossier"
expect(page).to have_selector("option", text: "scientifique")
expect(page).not_to have_selector("option", text: "Groupe inactif")
select(new_group, from: 'dossier_groupe_instructeur_id')
wait_for_autosave(false)
expect(page).to have_text(new_group)
click_on 'Déposer les modifications'
log_out
end
def register_instructeur_and_log_in(email)
confirmation_email = emails_sent_to(email)
.find { |m| m.subject == 'Activez votre compte instructeur' }
token_params = confirmation_email.body.match(/token=[^"]+/)
visit "users/activate?#{token_params}"
fill_in :user_password, with: password
click_button 'Définir le mot de passe'
expect(page).to have_text('Mot de passe enregistré')
end
end

View file

@ -10,28 +10,60 @@ describe 'The routing with rules', js: true do
p.draft_revision.add_type_de_champ(
type_champ: :drop_down_list,
libelle: 'Spécialité',
options: { "drop_down_other" => "0", "drop_down_options" => ["", "littéraire", "scientifique"] }
options: { "drop_down_other" => "0", "drop_down_options" => ["", "littéraire", "scientifique", "artistique"] }
)
end
end
let(:administrateur) { create(:administrateur, procedures: [procedure]) }
let(:scientifique_user) { create(:user, password: password) }
let(:litteraire_user) { create(:user, password: password) }
let(:artistique_user) { create(:user, password: password) }
before do
Flipper.enable(:routing_rules, procedure)
procedure.defaut_groupe_instructeur.instructeurs << administrateur.instructeur
end
scenario 'works' do
login_as administrateur.user, scope: :user
visit admin_procedure_path(procedure.id)
find('#groupe-instructeurs').click
scenario 'Routage à partir dun champ' do
steps_to_routing_configuration
# add littéraire groupe
fill_in 'Ajouter un nom de groupe', with: 'littéraire'
click_on 'Ajouter le groupe'
expect(page).to have_text('Le groupe dinstructeurs « littéraire » a été créé et le routage a été activé.')
choose('À partir dun champ', allow_label_click: true)
click_on 'Continuer'
expect(page).to have_text('Routage à partir dun champ')
choose('Spécialité', allow_label_click: true)
click_on 'Créer les groupes'
expect(page).to have_text('Gestion des groupes')
expect(page).to have_text('3 groupes')
expect(page).not_to have_text('À configurer')
click_on 'littéraire'
expect(page).to have_select("targeted_champ", selected: "Spécialité")
expect(page).to have_select("value", selected: "littéraire")
click_on '3 groupes'
click_on 'scientifique'
expect(page).to have_select("targeted_champ", selected: "Spécialité")
expect(page).to have_select("value", selected: "scientifique")
end
scenario 'Routage personnalisé' do
steps_to_routing_configuration
choose('Avancé', allow_label_click: true)
click_on 'Continuer'
expect(page).to have_text('Gestion des groupes')
# update defaut groupe
click_on 'défaut'
expect(page).to have_text('Paramètres principaux')
fill_in 'Nom du groupe', with: 'littéraire'
click_on 'Renommer'
expect(page).to have_text('Le nom est à présent « littéraire ». ')
# add victor to littéraire groupe
fill_in 'Emails', with: 'victor@inst.com'
@ -48,17 +80,18 @@ describe 'The routing with rules', js: true do
superwoman = User.find_by(email: 'superwoman@inst.com').instructeur
# add inactive groupe
click_on 'Groupes dinstructeurs'
fill_in 'Ajouter un nom de groupe', with: 'non visible car inactif'
click_on 'Ajouter le groupe'
check "Groupe inactif"
click_on 'Modifier'
click_on 'Ajout de groupes'
fill_in 'Nouveau groupe', with: 'non visible car inactif'
click_on 'Ajouter'
expect(page).to have_text('Le groupe dinstructeurs « non visible car inactif » a été créé. ')
check("Groupe inactif", allow_label_click: true)
# add scientifique groupe
click_on 'Groupes dinstructeurs'
fill_in 'Ajouter un nom de groupe', with: 'scientifique'
click_on 'Ajouter le groupe'
expect(page).to have_text('Le groupe dinstructeurs « scientifique » a été créé.')
# # add scientifique groupe
click_on '3 groupes'
click_on 'défaut bis'
fill_in 'Nom du groupe', with: 'scientifique'
click_on 'Renommer'
expect(page).to have_text('Le nom est à présent « scientifique ». ')
# add marie to scientifique groupe
fill_in 'Emails', with: 'marie@inst.com'
@ -73,30 +106,33 @@ describe 'The routing with rules', js: true do
expect(page).to have_text("Linstructeur superwoman@inst.com a été affecté")
# add routing rules
click_on 'Groupes dinstructeurs'
within('.target') { select('Spécialité') }
within('.value') { select('scientifique') }
h = procedure.groupe_instructeurs.index_by(&:label).transform_values(&:id)
click_on '3 groupes'
within(".gi-#{h['scientifique']}") do
within('.target') { select('Spécialité') }
within('.value') { select('scientifique') }
end
click_on 'littéraire'
within(".gi-#{h['littéraire']}") do
within('.target') { select('Spécialité') }
within('.value') { select('littéraire') }
end
within('.target') { select('Spécialité') }
within('.value') { select('littéraire') }
not_defauts = procedure.groupe_instructeurs.filter { |gi| ['littéraire', 'scientifique'].include?(gi.label) }
not_defauts.each { |gi| wait_until { gi.reload.routing_rule.present? } }
procedure.groupe_instructeurs.where(closed: false).each { |gi| wait_until { gi.reload.routing_rule.present? } }
# add a group without routing rules
click_on 'Ajout de groupes'
fill_in 'Nouveau groupe', with: 'artistique'
click_on 'Ajouter'
expect(page).to have_text('Le groupe dinstructeurs « artistique » a été créé. ')
expect(procedure.groupe_instructeurs.count).to eq(4)
# publish
publish_procedure(procedure)
log_out
# 2 users fill a dossier in each group
# 3 users fill a dossier in each group
user_send_dossier(scientifique_user, 'scientifique')
user_send_dossier(litteraire_user, 'littéraire')
user_send_dossier(artistique_user, 'artistique')
# the litteraires instructeurs only manage the litteraires dossiers
register_instructeur_and_log_in(victor.email)
@ -179,7 +215,7 @@ describe 'The routing with rules', js: true do
expect(find('.procedure-stats')).not_to have_css('span.notifications')
log_out
# the instructeurs who belong to scientifique AND litteraire groups manage scientifique and litterraire dossiers
# the instructeurs who belong to scientifique AND litteraire groups manage scientifique and litteraire dossiers
register_instructeur_and_log_in(superwoman.email)
visit instructeur_procedure_path(procedure, params: { statut: 'tous' })
expect(page).to have_text(litteraire_user.email)
@ -263,4 +299,15 @@ describe 'The routing with rules', js: true do
expect(page).to have_text('Mot de passe enregistré')
end
def steps_to_routing_configuration
login_as administrateur.user, scope: :user
visit admin_procedure_path(procedure.id)
find('#groupe-instructeurs').click
click_on 'Options'
expect(page).to have_text('Options concernant linstruction')
click_on 'Configurer le routage'
expect(page).to have_text('Choix du type de routage')
end
end