Merge pull request #9604 from demarches-simplifiees/etq-admin-je-peux-creer-une-regle-de-routage-de-plusieurs-lignes
ETQ admin je peux créer une règle de routage de plusieurs lignes
This commit is contained in:
commit
f7eeb21425
36 changed files with 486 additions and 302 deletions
44
app/components/conditions/champs_conditions_component.rb
Normal file
44
app/components/conditions/champs_conditions_component.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class Conditions::ChampsConditionsComponent < Conditions::ConditionsComponent
|
||||||
|
def initialize(tdc:, upper_tdcs:, procedure_id:)
|
||||||
|
@tdc, @condition, @source_tdcs = tdc, tdc.condition, upper_tdcs
|
||||||
|
@procedure_id = procedure_id
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def logic_conditionnel_button
|
||||||
|
html_class = 'fr-btn fr-btn--tertiary fr-btn--sm'
|
||||||
|
|
||||||
|
if @condition.nil?
|
||||||
|
submit_tag(
|
||||||
|
t('.enable_conditionnel'),
|
||||||
|
formaction: add_condition_path,
|
||||||
|
class: html_class
|
||||||
|
)
|
||||||
|
else
|
||||||
|
submit_tag(
|
||||||
|
t('.disable_conditionnel'),
|
||||||
|
formmethod: 'delete',
|
||||||
|
formnovalidate: true,
|
||||||
|
data: { confirm: t('.disable_conditionnel_alert') },
|
||||||
|
class: html_class
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_condition_path
|
||||||
|
add_row_admin_procedure_condition_path(@procedure_id, @tdc.stable_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_condition_path(row_index)
|
||||||
|
delete_row_admin_procedure_condition_path(@procedure_id, @tdc.stable_id, row_index: row_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_id_for(name, row_index)
|
||||||
|
"#{@tdc.stable_id}-#{name}-#{row_index}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_prefix
|
||||||
|
'type_de_champ[condition_form]'
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,7 +6,7 @@
|
||||||
Logique conditionnelle
|
Logique conditionnelle
|
||||||
= logic_conditionnel_button
|
= logic_conditionnel_button
|
||||||
|
|
||||||
= render TypesDeChampEditor::ConditionsErrorsComponent.new(conditions: condition_per_row, upper_tdcs: @upper_tdcs)
|
= render Conditions::ConditionsErrorsComponent.new(conditions: condition_per_row, source_tdcs: @source_tdcs)
|
||||||
|
|
||||||
- if @condition.present?
|
- if @condition.present?
|
||||||
%table.condition-table.mt-2.width-100
|
%table.condition-table.mt-2.width-100
|
|
@ -1,11 +1,6 @@
|
||||||
class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
class Conditions::ConditionsComponent < ApplicationComponent
|
||||||
include Logic
|
include Logic
|
||||||
|
|
||||||
def initialize(tdc:, upper_tdcs:, procedure_id:)
|
|
||||||
@tdc, @condition, @upper_tdcs = tdc, tdc.condition, upper_tdcs
|
|
||||||
@procedure_id = procedure_id
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def rows
|
def rows
|
||||||
|
@ -20,26 +15,6 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def logic_conditionnel_button
|
|
||||||
html_class = 'fr-btn fr-btn--tertiary fr-btn--sm'
|
|
||||||
|
|
||||||
if @condition.nil?
|
|
||||||
submit_tag(
|
|
||||||
t('.enable_conditionnel'),
|
|
||||||
formaction: add_row_admin_procedure_condition_path(@procedure_id, @tdc.stable_id),
|
|
||||||
class: html_class
|
|
||||||
)
|
|
||||||
else
|
|
||||||
submit_tag(
|
|
||||||
t('.disable_conditionnel'),
|
|
||||||
formmethod: 'delete',
|
|
||||||
formnovalidate: true,
|
|
||||||
data: { confirm: t('.disable_conditionnel_alert') },
|
|
||||||
class: html_class
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def far_left_tag(row_number)
|
def far_left_tag(row_number)
|
||||||
if row_number == 0
|
if row_number == 0
|
||||||
t('.display_if')
|
t('.display_if')
|
||||||
|
@ -85,7 +60,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
def available_targets_for_select
|
def available_targets_for_select
|
||||||
@upper_tdcs
|
@source_tdcs
|
||||||
.filter { |tdc| ChampValue::MANAGED_TYPE_DE_CHAMP.values.include?(tdc.type_champ) }
|
.filter { |tdc| ChampValue::MANAGED_TYPE_DE_CHAMP.values.include?(tdc.type_champ) }
|
||||||
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
|
.map { |tdc| [tdc.libelle, champ_value(tdc.stable_id).to_json] }
|
||||||
end
|
end
|
||||||
|
@ -108,7 +83,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
def compatibles_operators_for_select(left)
|
def compatibles_operators_for_select(left)
|
||||||
case left.type(@upper_tdcs)
|
case left.type(@source_tdcs)
|
||||||
when ChampValue::CHAMP_VALUE_TYPE.fetch(:boolean)
|
when ChampValue::CHAMP_VALUE_TYPE.fetch(:boolean)
|
||||||
[
|
[
|
||||||
[t('is', scope: 'logic'), Eq.name]
|
[t('is', scope: 'logic'), Eq.name]
|
||||||
|
@ -138,7 +113,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
def right_operand_tag(left, right, row_index)
|
def right_operand_tag(left, right, row_index)
|
||||||
right_invalid = !current_right_valid?(left, right)
|
right_invalid = !current_right_valid?(left, right)
|
||||||
|
|
||||||
case left.type(@upper_tdcs)
|
case left.type(@source_tdcs)
|
||||||
when :boolean
|
when :boolean
|
||||||
booleans_for_select = [[t('utils.yes'), constant(true).to_json], [t('utils.no'), constant(false).to_json]]
|
booleans_for_select = [[t('utils.yes'), constant(true).to_json], [t('utils.no'), constant(false).to_json]]
|
||||||
|
|
||||||
|
@ -160,7 +135,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
class: 'fr-select'
|
class: 'fr-select'
|
||||||
)
|
)
|
||||||
when :enum, :enums
|
when :enum, :enums
|
||||||
enums_for_select = left.options(@upper_tdcs)
|
enums_for_select = left.options(@source_tdcs)
|
||||||
|
|
||||||
if right_invalid
|
if right_invalid
|
||||||
enums_for_select = empty_target_for_select + enums_for_select
|
enums_for_select = empty_target_for_select + enums_for_select
|
||||||
|
@ -186,13 +161,13 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_right_valid?(left, right)
|
def current_right_valid?(left, right)
|
||||||
Logic.compatible_type?(left, right, @upper_tdcs)
|
Logic.compatible_type?(left, right, @source_tdcs)
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_condition_tag
|
def add_condition_tag
|
||||||
tag.button(
|
tag.button(
|
||||||
t('.add_condition'),
|
t('.add_condition'),
|
||||||
formaction: add_row_admin_procedure_condition_path(@procedure_id, @tdc.stable_id),
|
formaction: add_condition_path,
|
||||||
formnovalidate: true,
|
formnovalidate: true,
|
||||||
class: 'fr-btn fr-btn--secondary fr-btn--sm fr-icon-add-circle-line fr-btn--icon-left'
|
class: 'fr-btn fr-btn--secondary fr-btn--sm fr-icon-add-circle-line fr-btn--icon-left'
|
||||||
)
|
)
|
||||||
|
@ -201,7 +176,7 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
def delete_condition_tag(row_index)
|
def delete_condition_tag(row_index)
|
||||||
tag.button(
|
tag.button(
|
||||||
tag.span('', class: 'icon delete') + tag.span(t('.remove_a_row'), class: 'sr-only'),
|
tag.span('', class: 'icon delete') + tag.span(t('.remove_a_row'), class: 'sr-only'),
|
||||||
formaction: delete_row_admin_procedure_condition_path(@procedure_id, @tdc.stable_id, row_index: row_index),
|
formaction: delete_condition_path(row_index),
|
||||||
formmethod: 'delete',
|
formmethod: 'delete',
|
||||||
formnovalidate: true
|
formnovalidate: true
|
||||||
)
|
)
|
||||||
|
@ -214,12 +189,4 @@ class TypesDeChampEditor::ConditionsComponent < ApplicationComponent
|
||||||
def input_name_for(name)
|
def input_name_for(name)
|
||||||
"#{input_prefix}[rows][][#{name}]"
|
"#{input_prefix}[rows][][#{name}]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def input_id_for(name, row_index)
|
|
||||||
"#{@tdc.stable_id}-#{name}-#{row_index}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def input_prefix
|
|
||||||
'type_de_champ[condition_form]'
|
|
||||||
end
|
|
||||||
end
|
end
|
|
@ -1,13 +1,13 @@
|
||||||
class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
class Conditions::ConditionsErrorsComponent < ApplicationComponent
|
||||||
def initialize(conditions:, upper_tdcs:)
|
def initialize(conditions:, source_tdcs:)
|
||||||
@conditions, @upper_tdcs = conditions, upper_tdcs
|
@conditions, @source_tdcs = conditions, source_tdcs
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def errors
|
def errors
|
||||||
errors = @conditions
|
errors = @conditions
|
||||||
.flat_map { |condition| condition.errors(@upper_tdcs) }
|
.flat_map { |condition| condition.errors(@source_tdcs) }
|
||||||
.uniq
|
.uniq
|
||||||
|
|
||||||
# if a tdc is not available (has been removed for example)
|
# if a tdc is not available (has been removed for example)
|
||||||
|
@ -34,13 +34,13 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
||||||
in { type: :incompatible, stable_id: nil }
|
in { type: :incompatible, stable_id: nil }
|
||||||
t('not_available', scope: '.errors')
|
t('not_available', scope: '.errors')
|
||||||
in { type: :unmanaged, stable_id: stable_id }
|
in { type: :unmanaged, stable_id: stable_id }
|
||||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
targeted_champ = @source_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
||||||
t('unmanaged',
|
t('unmanaged',
|
||||||
scope: '.errors',
|
scope: '.errors',
|
||||||
libelle: targeted_champ.libelle,
|
libelle: targeted_champ.libelle,
|
||||||
type_champ: t(targeted_champ.type_champ, scope: 'activerecord.attributes.type_de_champ.type_champs')&.downcase)
|
type_champ: t(targeted_champ.type_champ, scope: 'activerecord.attributes.type_de_champ.type_champs')&.downcase)
|
||||||
in { type: :incompatible, stable_id: stable_id, right: right, operator_name: operator_name }
|
in { type: :incompatible, stable_id: stable_id, right: right, operator_name: operator_name }
|
||||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
targeted_champ = @source_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
||||||
t('incompatible', scope: '.errors',
|
t('incompatible', scope: '.errors',
|
||||||
libelle: targeted_champ.libelle,
|
libelle: targeted_champ.libelle,
|
||||||
type_champ: t(targeted_champ.type_champ, scope: 'activerecord.attributes.type_de_champ.type_champs')&.downcase,
|
type_champ: t(targeted_champ.type_champ, scope: 'activerecord.attributes.type_de_champ.type_champs')&.downcase,
|
||||||
|
@ -50,7 +50,7 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
||||||
t('required_number', scope: '.errors',
|
t('required_number', scope: '.errors',
|
||||||
operator: t(operator_name, scope: 'logic.operators'))
|
operator: t(operator_name, scope: 'logic.operators'))
|
||||||
in { type: :not_included, stable_id: stable_id, right: right }
|
in { type: :not_included, stable_id: stable_id, right: right }
|
||||||
targeted_champ = @upper_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
targeted_champ = @source_tdcs.find { |tdc| tdc.stable_id == stable_id }
|
||||||
t('not_included', scope: '.errors',
|
t('not_included', scope: '.errors',
|
||||||
libelle: targeted_champ.libelle,
|
libelle: targeted_champ.libelle,
|
||||||
right: right.to_s.downcase)
|
right: right.to_s.downcase)
|
||||||
|
@ -67,7 +67,7 @@ class TypesDeChampEditor::ConditionsErrorsComponent < ApplicationComponent
|
||||||
|
|
||||||
def render?
|
def render?
|
||||||
@conditions
|
@conditions
|
||||||
.filter { |condition| condition.errors(@upper_tdcs).present? }
|
.filter { |condition| condition.errors(@source_tdcs).present? }
|
||||||
.present?
|
.present?
|
||||||
end
|
end
|
||||||
end
|
end
|
28
app/components/conditions/routing_rules_component.rb
Normal file
28
app/components/conditions/routing_rules_component.rb
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
class Conditions::RoutingRulesComponent < Conditions::ConditionsComponent
|
||||||
|
include Logic
|
||||||
|
|
||||||
|
def initialize(groupe_instructeur:)
|
||||||
|
@groupe_instructeur = groupe_instructeur
|
||||||
|
@condition = groupe_instructeur.routing_rule || empty_operator(empty, empty)
|
||||||
|
@procedure_id = groupe_instructeur.procedure_id
|
||||||
|
@source_tdcs = groupe_instructeur.procedure.active_revision.types_de_champ_public
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def add_condition_path
|
||||||
|
add_row_admin_procedure_routing_rule_path(@procedure_id, @groupe_instructeur.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_condition_path(row_index)
|
||||||
|
delete_row_admin_procedure_routing_rule_path(@procedure_id, @groupe_instructeur.id, row_index: row_index)
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_id_for(name, row_index)
|
||||||
|
"#{@groupe_instructeur.id}-#{name}-#{row_index}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def input_prefix
|
||||||
|
'groupe_instructeur[condition_form]'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
fr:
|
||||||
|
display_if: Router si
|
||||||
|
select: Sélectionner
|
||||||
|
add_condition: Ajouter une règle de routage
|
||||||
|
remove_a_row: Supprimer la ligne
|
|
@ -0,0 +1,32 @@
|
||||||
|
.flex.justify-start.section{ id: dom_id(@groupe_instructeur, :routing_rule) }
|
||||||
|
= form_tag admin_procedure_routing_rule_path(@procedure_id, @groupe_instructeur.id),
|
||||||
|
method: :patch,
|
||||||
|
data: { turbo: true, controller: 'autosave' },
|
||||||
|
class: 'form width-100' do
|
||||||
|
.conditionnel.mt-2.width-100
|
||||||
|
.flex
|
||||||
|
- if @groupe_instructeur.invalid_rule?
|
||||||
|
%p.fr-mb-1w.fr-badge.fr-badge--warning.fr-badge--sm règle invalide
|
||||||
|
- elsif @groupe_instructeur.non_unique_rule?
|
||||||
|
%p.fr-mb-1w.fr-badge.fr-badge--warning.fr-badge--sm règle déjà attribuée à #{@groupe_instructeur.groups_with_same_rule}
|
||||||
|
|
||||||
|
= render Conditions::ConditionsErrorsComponent.new(conditions: condition_per_row, source_tdcs: @source_tdcs)
|
||||||
|
|
||||||
|
%table.condition-table.mt-2
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th.far-left
|
||||||
|
%th.target Champ Cible
|
||||||
|
%th.operator Opérateur
|
||||||
|
%th.value Valeur
|
||||||
|
%th.delete-column
|
||||||
|
%tbody
|
||||||
|
- rows.each.with_index do |(targeted_champ, operator_name, value), row_index|
|
||||||
|
%tr
|
||||||
|
%td.far-left= far_left_tag(row_index)
|
||||||
|
%td.target= left_operand_tag(targeted_champ, row_index)
|
||||||
|
%td.operator= operator_tag(operator_name, targeted_champ, row_index)
|
||||||
|
%td.value= right_operand_tag(targeted_champ, value, row_index)
|
||||||
|
%td.delete-column= delete_condition_tag(row_index)
|
||||||
|
- if @groupe_instructeur.procedure.feature_enabled?(:multi_line_routing)
|
||||||
|
.flex.justify-end.mt-2= add_condition_tag
|
|
@ -22,34 +22,14 @@
|
||||||
Groupe inactif
|
Groupe inactif
|
||||||
%span.fr-hint-text Si cette option est activée, les usagers ne pourront plus sélectionner ce groupe d’instructeurs
|
%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),
|
= render Conditions::RoutingRulesComponent.new(groupe_instructeur: @groupe_instructeur)
|
||||||
method: :post,
|
|
||||||
data: { controller: 'autosave' },
|
|
||||||
class: 'fr-mb-3w' do
|
|
||||||
|
|
||||||
= hidden_field_tag('groupe_instructeur_id', @groupe_instructeur.id)
|
.fr-hint-text.mt-2.mb-2
|
||||||
|
%span Si vous ne trouvez pas l'option correspondant à votre groupe, veuillez l'ajouter dans le
|
||||||
|
%span
|
||||||
|
= link_to 'champ concerné', champs_admin_procedure_path(@procedure)
|
||||||
|
|
||||||
.flex
|
.flex.fr-btns-group--sm.fr-btns-group--inline.fr-btns-group--icon-right.mb-2
|
||||||
%p.fr-mb-1w.fr-mr-2w Routage
|
|
||||||
- if @groupe_instructeur.invalid_rule?
|
|
||||||
%p.fr-mb-1w.fr-badge.fr-badge--warning.fr-badge--sm règle invalide
|
|
||||||
- elsif @groupe_instructeur.non_unique_rule?
|
|
||||||
%p.fr-mb-1w.fr-badge.fr-badge--warning.fr-badge--sm règle déjà attribuée à #{@groupe_instructeur.groups_with_same_rule}
|
|
||||||
|
|
||||||
.flex.align-baseline.fr-mb-1w
|
|
||||||
.fr-mr-2w.no-wrap si le champ
|
|
||||||
.target.fr-mr-2w
|
|
||||||
= targeted_champ_tag
|
|
||||||
.operator.fr-mr-2w.no-wrap
|
|
||||||
= operator_tag
|
|
||||||
.value
|
|
||||||
= value_tag
|
|
||||||
.fr-hint-text
|
|
||||||
%span Si vous ne trouvez pas l'option correspondant à votre groupe, veuillez l'ajouter dans le champ dédié au
|
|
||||||
%span
|
|
||||||
= link_to 'routage', champs_admin_procedure_path(@procedure)
|
|
||||||
|
|
||||||
.flex.fr-btns-group--sm.fr-btns-group--inline.fr-btns-group--icon-right
|
|
||||||
- if @groupe_instructeur.can_delete?
|
- if @groupe_instructeur.can_delete?
|
||||||
%p= t('.delete')
|
%p= t('.delete')
|
||||||
= button_to admin_procedure_groupe_instructeur_path(@procedure, @groupe_instructeur),
|
= button_to admin_procedure_groupe_instructeur_path(@procedure, @groupe_instructeur),
|
||||||
|
|
|
@ -134,7 +134,7 @@
|
||||||
.type-de-champ-add-button{ id: dom_id(coordinate, :type_de_champ_add_button), class: class_names(hidden: !coordinate.empty?) }
|
.type-de-champ-add-button{ id: dom_id(coordinate, :type_de_champ_add_button), class: class_names(hidden: !coordinate.empty?) }
|
||||||
= render TypesDeChampEditor::AddChampButtonComponent.new(revision: coordinate.revision, parent: coordinate, is_annotation: coordinate.private?)
|
= render TypesDeChampEditor::AddChampButtonComponent.new(revision: coordinate.revision, parent: coordinate, is_annotation: coordinate.private?)
|
||||||
|
|
||||||
= render(TypesDeChampEditor::ConditionsComponent.new(tdc: type_de_champ, upper_tdcs: @upper_coordinates.map(&:type_de_champ), procedure_id: procedure.id))
|
= render(Conditions::ChampsConditionsComponent.new(tdc: type_de_champ, upper_tdcs: @upper_coordinates.map(&:type_de_champ), procedure_id: procedure.id))
|
||||||
|
|
||||||
.type-de-champ-add-button{ class: class_names(root: !coordinate.child?) }
|
.type-de-champ-add-button{ class: class_names(root: !coordinate.child?) }
|
||||||
= render TypesDeChampEditor::AddChampButtonComponent.new(revision: coordinate.revision, parent: coordinate&.parent, is_annotation: coordinate.private?, after_stable_id: type_de_champ.stable_id)
|
= render TypesDeChampEditor::AddChampButtonComponent.new(revision: coordinate.revision, parent: coordinate&.parent, is_annotation: coordinate.private?, after_stable_id: type_de_champ.stable_id)
|
||||||
|
|
|
@ -42,7 +42,7 @@ module Administrateurs
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_condition_component
|
def build_condition_component
|
||||||
TypesDeChampEditor::ConditionsComponent.new(
|
Conditions::ChampsConditionsComponent.new(
|
||||||
tdc: @tdc,
|
tdc: @tdc,
|
||||||
upper_tdcs: @upper_tdcs,
|
upper_tdcs: @upper_tdcs,
|
||||||
procedure_id: @procedure.id
|
procedure_id: @procedure.id
|
||||||
|
@ -50,7 +50,7 @@ module Administrateurs
|
||||||
end
|
end
|
||||||
|
|
||||||
def condition_form
|
def condition_form
|
||||||
ConditionForm.new(condition_params.merge({ upper_tdcs: @upper_tdcs }))
|
ConditionForm.new(condition_params.merge({ source_tdcs: @upper_tdcs }))
|
||||||
end
|
end
|
||||||
|
|
||||||
def retrieve_coordinate_and_uppers
|
def retrieve_coordinate_and_uppers
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
module Administrateurs
|
|
||||||
class RoutingController < AdministrateurController
|
|
||||||
include Logic
|
|
||||||
|
|
||||||
before_action :retrieve_procedure
|
|
||||||
|
|
||||||
def update
|
|
||||||
left = targeted_champ
|
|
||||||
|
|
||||||
right = targeted_champ_changed? ? empty : value
|
|
||||||
|
|
||||||
new_routing_rule = case operator_name
|
|
||||||
when Eq.name
|
|
||||||
ds_eq(left, right)
|
|
||||||
when NotEq.name
|
|
||||||
ds_not_eq(left, right)
|
|
||||||
end
|
|
||||||
groupe_instructeur.update!(routing_rule: new_routing_rule)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update_defaut_groupe_instructeur
|
|
||||||
new_defaut = @procedure.groupe_instructeurs.find(defaut_groupe_instructeur_id)
|
|
||||||
@procedure.update!(defaut_groupe_instructeur: new_defaut)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def targeted_champ_changed?
|
|
||||||
targeted_champ != groupe_instructeur.routing_rule&.left
|
|
||||||
end
|
|
||||||
|
|
||||||
def targeted_champ
|
|
||||||
Logic.from_json(params[:targeted_champ])
|
|
||||||
end
|
|
||||||
|
|
||||||
def operator_name
|
|
||||||
params[:operator_name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def value
|
|
||||||
Logic.from_json(params[:value])
|
|
||||||
end
|
|
||||||
|
|
||||||
def groupe_instructeur
|
|
||||||
@groupe_instructeur ||= @procedure.groupe_instructeurs.find(groupe_instructeur_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def groupe_instructeur_id
|
|
||||||
params[:groupe_instructeur_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
def defaut_groupe_instructeur_id
|
|
||||||
params[:defaut_groupe_instructeur_id]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
79
app/controllers/administrateurs/routing_rules_controller.rb
Normal file
79
app/controllers/administrateurs/routing_rules_controller.rb
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
module Administrateurs
|
||||||
|
class RoutingRulesController < AdministrateurController
|
||||||
|
include Logic
|
||||||
|
before_action :retrieve_procedure, :retrieve_tdcs
|
||||||
|
before_action :retrieve_groupe_instructeur, except: [:update_defaut_groupe_instructeur]
|
||||||
|
|
||||||
|
def update
|
||||||
|
condition = condition_form.to_condition
|
||||||
|
@groupe_instructeur.update!(routing_rule: condition)
|
||||||
|
|
||||||
|
@routing_rule_component = build_routing_rule_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_row
|
||||||
|
condition = Logic.add_empty_condition_to(@groupe_instructeur.routing_rule)
|
||||||
|
@groupe_instructeur.update!(routing_rule: condition)
|
||||||
|
|
||||||
|
@routing_rule_component = build_routing_rule_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_row
|
||||||
|
condition = condition_form.delete_row(row_index).to_condition
|
||||||
|
@groupe_instructeur.update!(routing_rule: condition)
|
||||||
|
|
||||||
|
@routing_rule_component = build_routing_rule_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_targeted_champ
|
||||||
|
condition = condition_form.change_champ(row_index).to_condition
|
||||||
|
@groupe_instructeur.update!(routing_rule: condition)
|
||||||
|
|
||||||
|
@routing_rule_component = build_routing_rule_component
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_defaut_groupe_instructeur
|
||||||
|
new_defaut = @procedure.groupe_instructeurs.find(defaut_groupe_instructeur_id)
|
||||||
|
@procedure.update!(defaut_groupe_instructeur: new_defaut)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def groupe_instructeur_id
|
||||||
|
params[:groupe_instructeur_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def defaut_groupe_instructeur_id
|
||||||
|
params[:defaut_groupe_instructeur_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_routing_rule_component
|
||||||
|
Conditions::RoutingRulesComponent.new(
|
||||||
|
groupe_instructeur: @groupe_instructeur
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def condition_form
|
||||||
|
ConditionForm.new(routing_rule_params.merge({ source_tdcs: @source_tdcs }))
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve_tdcs
|
||||||
|
@source_tdcs = @procedure.active_revision.types_de_champ
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieve_groupe_instructeur
|
||||||
|
@groupe_instructeur = @procedure.groupe_instructeurs.find(groupe_instructeur_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def routing_rule_params
|
||||||
|
params
|
||||||
|
.require(:groupe_instructeur)
|
||||||
|
.require(:condition_form)
|
||||||
|
.permit(:top_operator_name, rows: [:targeted_champ, :operator_name, :value])
|
||||||
|
end
|
||||||
|
|
||||||
|
def row_index
|
||||||
|
params[:row_index].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,7 +2,7 @@ class ConditionForm
|
||||||
include ActiveModel::Model
|
include ActiveModel::Model
|
||||||
include Logic
|
include Logic
|
||||||
|
|
||||||
attr_accessor :top_operator_name, :rows, :upper_tdcs
|
attr_accessor :top_operator_name, :rows, :source_tdcs
|
||||||
|
|
||||||
def to_condition
|
def to_condition
|
||||||
case sub_conditions.count
|
case sub_conditions.count
|
||||||
|
@ -22,7 +22,7 @@ class ConditionForm
|
||||||
end
|
end
|
||||||
|
|
||||||
def change_champ(i)
|
def change_champ(i)
|
||||||
sub_conditions[i] = Logic.ensure_compatibility_from_left(sub_conditions[i], upper_tdcs)
|
sub_conditions[i] = Logic.ensure_compatibility_from_left(sub_conditions[i], source_tdcs)
|
||||||
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
@ -39,7 +39,7 @@ class ConditionForm
|
||||||
|
|
||||||
def row_to_condition(row)
|
def row_to_condition(row)
|
||||||
left = Logic.from_json(row[:targeted_champ])
|
left = Logic.from_json(row[:targeted_champ])
|
||||||
right = parse_value(left.type(upper_tdcs), row[:value])
|
right = parse_value(left.type(source_tdcs), row[:value])
|
||||||
|
|
||||||
Logic.class_from_name(row[:operator_name]).new(left, right)
|
Logic.class_from_name(row[:operator_name]).new(left, right)
|
||||||
end
|
end
|
||||||
|
|
|
@ -84,7 +84,15 @@ class GroupeInstructeur < ApplicationRecord
|
||||||
|
|
||||||
def valid_rule?
|
def valid_rule?
|
||||||
return false if routing_rule.nil?
|
return false if routing_rule.nil?
|
||||||
([routing_rule.left, routing_rule, routing_rule.right] in [ChampValue, Eq | NotEq, Constant]) && routing_rule_matches_tdc?
|
if [And, Or].include?(routing_rule.class)
|
||||||
|
routing_rule.operands.all? { |rule_line| valid_rule_line?(rule_line) }
|
||||||
|
else
|
||||||
|
valid_rule_line?(routing_rule)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_rule_line?(rule)
|
||||||
|
([rule.left, rule, rule.right] in [ChampValue, (LessThan | LessThanEq | Eq | NotEq | GreaterThanEq | GreaterThan | IncludeOperator), Constant]) && routing_rule_matches_tdc?(rule)
|
||||||
end
|
end
|
||||||
|
|
||||||
def non_unique_rule?
|
def non_unique_rule?
|
||||||
|
@ -95,7 +103,8 @@ class GroupeInstructeur < ApplicationRecord
|
||||||
def groups_with_same_rule
|
def groups_with_same_rule
|
||||||
return if routing_rule.nil?
|
return if routing_rule.nil?
|
||||||
other_groupe_instructeurs
|
other_groupe_instructeurs
|
||||||
.filter { |gi| !gi.routing_rule.nil? && gi.routing_rule.right != empty && gi.routing_rule == routing_rule }
|
.filter { _1.routing_rule.present? }
|
||||||
|
.filter { _1.routing_rule == routing_rule }
|
||||||
.map(&:label)
|
.map(&:label)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
end
|
end
|
||||||
|
@ -106,18 +115,9 @@ class GroupeInstructeur < ApplicationRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def routing_rule_matches_tdc?
|
def routing_rule_matches_tdc?(rule)
|
||||||
routing_tdc = procedure.active_revision.types_de_champ.find_by(stable_id: routing_rule.left.stable_id)
|
tdcs = procedure.active_revision.types_de_champ_public
|
||||||
|
rule.errors(tdcs).blank?
|
||||||
options = case routing_tdc.type_champ
|
|
||||||
when TypeDeChamp.type_champs.fetch(:communes), TypeDeChamp.type_champs.fetch(:departements), TypeDeChamp.type_champs.fetch(:epci)
|
|
||||||
APIGeoService.departements.map { _1[:code] }
|
|
||||||
when TypeDeChamp.type_champs.fetch(:regions)
|
|
||||||
APIGeoService.regions.map { _1[:code] }
|
|
||||||
when TypeDeChamp.type_champs.fetch(:drop_down_list)
|
|
||||||
routing_tdc.drop_down_list_enabled_non_empty_options(other: true).map { _1.is_a?(Array) ? _1.last : _1 }
|
|
||||||
end
|
|
||||||
routing_rule.right.value.in?(options)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
serialize :routing_rule, LogicSerializer
|
serialize :routing_rule, LogicSerializer
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class Logic::EmptyOperator < Logic::BinaryOperator
|
class Logic::EmptyOperator < Logic::BinaryOperator
|
||||||
def to_s(_type_de_champs = []) = "empty operator"
|
def to_s(_type_de_champs = []) = I18n.t('logic.empty_operator')
|
||||||
|
|
||||||
def type(_type_de_champs = []) = :empty
|
def type(_type_de_champs = []) = :empty
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
- rendered = render @routing_rule_component
|
||||||
|
|
||||||
|
- if rendered.present?
|
||||||
|
= turbo_stream.replace dom_id(@groupe_instructeur, :routing_rule) do
|
||||||
|
- rendered
|
||||||
|
- else
|
||||||
|
= turbo_stream.remove dom_id(@groupe_instructeur, :routing_rule)
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -0,0 +1 @@
|
||||||
|
= render partial: 'update'
|
|
@ -20,7 +20,8 @@ features = [
|
||||||
:groupe_instructeur_api_hack,
|
:groupe_instructeur_api_hack,
|
||||||
:rerouting,
|
:rerouting,
|
||||||
:cojo_type_de_champ,
|
:cojo_type_de_champ,
|
||||||
:sva
|
:sva,
|
||||||
|
:multi_line_routing
|
||||||
]
|
]
|
||||||
|
|
||||||
def database_exists?
|
def database_exists?
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
en:
|
en:
|
||||||
logic:
|
logic:
|
||||||
empty: empty member
|
empty: empty member
|
||||||
|
empty_operator: empty operator
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
fr:
|
fr:
|
||||||
logic:
|
logic:
|
||||||
empty: un membre vide
|
empty: un membre vide
|
||||||
|
empty_operator: un opérateur vide
|
||||||
is: Est
|
is: Est
|
||||||
is_not: N’est pas
|
is_not: N’est pas
|
||||||
operators:
|
operators:
|
||||||
|
|
|
@ -548,8 +548,13 @@ Rails.application.routes.draw do
|
||||||
delete :delete_row, on: :member
|
delete :delete_row, on: :member
|
||||||
end
|
end
|
||||||
|
|
||||||
patch :update, controller: 'routing', as: :routing_rules
|
resources :routing_rules, only: [:update, :destroy], param: :groupe_instructeur_id do
|
||||||
patch :update_defaut_groupe_instructeur, controller: 'routing', as: :update_defaut_groupe_instructeur
|
patch :add_row, on: :member
|
||||||
|
patch :change_targeted_champ, on: :member
|
||||||
|
delete :delete_row, on: :member
|
||||||
|
end
|
||||||
|
|
||||||
|
patch :update_defaut_groupe_instructeur, controller: 'routing_rules', as: :update_defaut_groupe_instructeur
|
||||||
|
|
||||||
put 'clone'
|
put 'clone'
|
||||||
put 'archive'
|
put 'archive'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
describe TypesDeChampEditor::ConditionsComponent, type: :component do
|
describe Conditions::ChampsConditionsComponent, type: :component do
|
||||||
include Logic
|
include Logic
|
||||||
|
|
||||||
describe 'render' do
|
describe 'render' do
|
52
spec/components/conditions/routing_rules_component_spec.rb
Normal file
52
spec/components/conditions/routing_rules_component_spec.rb
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
describe Conditions::RoutingRulesComponent, type: :component do
|
||||||
|
include Logic
|
||||||
|
|
||||||
|
describe 'render' do
|
||||||
|
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }, { type: :integer_number, libelle: 'Un champ nombre entier' }]) }
|
||||||
|
let(:groupe_instructeur) { procedure.groupe_instructeurs.first }
|
||||||
|
let(:drop_down_tdc) { procedure.draft_revision.types_de_champ.first }
|
||||||
|
let(:integer_number_tdc) { procedure.draft_revision.types_de_champ.last }
|
||||||
|
let(:routing_rule) { ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
groupe_instructeur.update(routing_rule: routing_rule)
|
||||||
|
render_inline(described_class.new(groupe_instructeur: groupe_instructeur))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with one row' do
|
||||||
|
context 'when routing rule is valid' do
|
||||||
|
it do
|
||||||
|
expect(page).to have_text('Champ Cible')
|
||||||
|
expect(page).not_to have_text('règle invalide')
|
||||||
|
expect(page).to have_select('groupe_instructeur[condition_form][rows][][operator_name]', options: ["Est", "N’est pas"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when routing rule is invalid' do
|
||||||
|
let(:routing_rule) { ds_eq(champ_value(drop_down_tdc.stable_id), empty) }
|
||||||
|
it { expect(page).to have_text('règle invalide') }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with two rows' do
|
||||||
|
context 'when routing rule is valid' do
|
||||||
|
let(:routing_rule) { ds_and([ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')), ds_not_eq(champ_value(integer_number_tdc.stable_id), constant(33))]) }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(page).not_to have_text('règle invalide')
|
||||||
|
expect(page).to have_selector('tbody > tr', count: 2)
|
||||||
|
expect(page).to have_select("groupe_instructeur_condition_form_top_operator_name", selected: "Et", options: ['Et', 'Ou'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when routing rule is invalid' do
|
||||||
|
let(:routing_rule) { ds_or([ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')), ds_not_eq(champ_value(integer_number_tdc.stable_id), empty)]) }
|
||||||
|
it do
|
||||||
|
expect(page).to have_text('règle invalide')
|
||||||
|
expect(page).to have_selector('tbody > tr', count: 2)
|
||||||
|
expect(page).to have_select("groupe_instructeur_condition_form_top_operator_name", selected: "Ou", options: ['Et', 'Ou'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,11 +1,11 @@
|
||||||
describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
describe Conditions::ConditionsErrorsComponent, type: :component do
|
||||||
include Logic
|
include Logic
|
||||||
|
|
||||||
describe 'render' do
|
describe 'render' do
|
||||||
let(:conditions) { [] }
|
let(:conditions) { [] }
|
||||||
let(:upper_tdcs) { [] }
|
let(:source_tdcs) { [] }
|
||||||
|
|
||||||
before { render_inline(described_class.new(conditions: conditions, upper_tdcs: upper_tdcs)) }
|
before { render_inline(described_class.new(conditions: conditions, source_tdcs: source_tdcs)) }
|
||||||
|
|
||||||
context 'when there are no condition' do
|
context 'when there are no condition' do
|
||||||
it { expect(page).to have_no_css('.errors-summary') }
|
it { expect(page).to have_no_css('.errors-summary') }
|
||||||
|
@ -23,7 +23,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when the targeted_champ is unmanaged' do
|
context 'when the targeted_champ is unmanaged' do
|
||||||
let(:tdc) { create(:type_de_champ_address) }
|
let(:tdc) { create(:type_de_champ_address) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(1))] }
|
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(1))] }
|
||||||
|
|
||||||
it do
|
it do
|
||||||
|
@ -34,7 +34,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when the types mismatch' do
|
context 'when the types mismatch' do
|
||||||
let(:tdc) { create(:type_de_champ_integer_number) }
|
let(:tdc) { create(:type_de_champ_integer_number) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant('a'))] }
|
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant('a'))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("Le champ « #{tdc.libelle} » est de type « nombre entier ». Il ne peut pas être égal à « a ».") }
|
it { expect(page).to have_content("Le champ « #{tdc.libelle} » est de type « nombre entier ». Il ne peut pas être égal à « a ».") }
|
||||||
|
@ -42,7 +42,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when a number operator is applied on not a number' do
|
context 'when a number operator is applied on not a number' do
|
||||||
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [greater_than(champ_value(tdc.stable_id), constant('a text'))] }
|
let(:conditions) { [greater_than(champ_value(tdc.stable_id), constant('a text'))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("« Supérieur à » ne s'applique qu'à des nombres.") }
|
it { expect(page).to have_content("« Supérieur à » ne s'applique qu'à des nombres.") }
|
||||||
|
@ -50,7 +50,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when the include operator is applied on a list' do
|
context 'when the include operator is applied on a list' do
|
||||||
let(:tdc) { create(:type_de_champ_integer_number) }
|
let(:tdc) { create(:type_de_champ_integer_number) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_include(champ_value(tdc.stable_id), constant('a text'))] }
|
let(:conditions) { [ds_include(champ_value(tdc.stable_id), constant('a text'))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("Lʼopérateur « inclus » ne s'applique qu'au choix simple ou multiple.") }
|
it { expect(page).to have_content("Lʼopérateur « inclus » ne s'applique qu'au choix simple ou multiple.") }
|
||||||
|
@ -58,7 +58,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when a choice is not in a drop_down' do
|
context 'when a choice is not in a drop_down' do
|
||||||
let(:tdc) { create(:type_de_champ_drop_down_list) }
|
let(:tdc) { create(:type_de_champ_drop_down_list) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant('another choice'))] }
|
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant('another choice'))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("« another choice » ne fait pas partie de « #{tdc.libelle} ».") }
|
it { expect(page).to have_content("« another choice » ne fait pas partie de « #{tdc.libelle} ».") }
|
||||||
|
@ -66,7 +66,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when a choice is not in a multiple_drop_down' do
|
context 'when a choice is not in a multiple_drop_down' do
|
||||||
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_include(champ_value(tdc.stable_id), constant('another choice'))] }
|
let(:conditions) { [ds_include(champ_value(tdc.stable_id), constant('another choice'))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("« another choice » ne fait pas partie de « #{tdc.libelle} ».") }
|
it { expect(page).to have_content("« another choice » ne fait pas partie de « #{tdc.libelle} ».") }
|
||||||
|
@ -74,7 +74,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when an eq operator applies to a multiple_drop_down' do
|
context 'when an eq operator applies to a multiple_drop_down' do
|
||||||
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(tdc.drop_down_list_enabled_non_empty_options.first))] }
|
let(:conditions) { [ds_eq(champ_value(tdc.stable_id), constant(tdc.drop_down_list_enabled_non_empty_options.first))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("« est » ne s'applique pas au choix multiple.") }
|
it { expect(page).to have_content("« est » ne s'applique pas au choix multiple.") }
|
||||||
|
@ -82,7 +82,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
|
|
||||||
context 'when an not_eq operator applies to a multiple_drop_down' do
|
context 'when an not_eq operator applies to a multiple_drop_down' do
|
||||||
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
let(:tdc) { create(:type_de_champ_multiple_drop_down_list) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_not_eq(champ_value(tdc.stable_id), constant(tdc.drop_down_list_enabled_non_empty_options.first))] }
|
let(:conditions) { [ds_not_eq(champ_value(tdc.stable_id), constant(tdc.drop_down_list_enabled_non_empty_options.first))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("« n’est pas » ne s'applique pas au choix multiple.") }
|
it { expect(page).to have_content("« n’est pas » ne s'applique pas au choix multiple.") }
|
||||||
|
@ -92,7 +92,7 @@ describe TypesDeChampEditor::ConditionsErrorsComponent, type: :component do
|
||||||
# Cf https://demarches-simplifiees.sentry.io/issues/3625488398/events/53164e105bc94d55a004d69f96d58fb2/?project=1429550
|
# Cf https://demarches-simplifiees.sentry.io/issues/3625488398/events/53164e105bc94d55a004d69f96d58fb2/?project=1429550
|
||||||
# However maybe we should not have empty at left with still a constant at right
|
# However maybe we should not have empty at left with still a constant at right
|
||||||
let(:tdc) { create(:type_de_champ_integer_number) }
|
let(:tdc) { create(:type_de_champ_integer_number) }
|
||||||
let(:upper_tdcs) { [tdc] }
|
let(:source_tdcs) { [tdc] }
|
||||||
let(:conditions) { [ds_eq(empty, constant('a text'))] }
|
let(:conditions) { [ds_eq(empty, constant('a text'))] }
|
||||||
|
|
||||||
it { expect(page).to have_content("Un champ cible n'est plus disponible") }
|
it { expect(page).to have_content("Un champ cible n'est plus disponible") }
|
||||||
|
|
|
@ -60,26 +60,18 @@ describe Administrateurs::GroupeInstructeursController, type: :controller do
|
||||||
end
|
end
|
||||||
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('règle invalide')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'group with routing rule matching tdc' do
|
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!(:drop_down_tdc) { create(:type_de_champ_drop_down_list, procedure: procedure, drop_down_options: options) }
|
||||||
let(:options) { procedure.groupe_instructeurs.pluck(:label) }
|
let(:options) { ['Premier choix', 'Deuxième choix', 'Troisième choix'] }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
gi_1_1.update(routing_rule: ds_eq(champ_value(drop_down_tdc.stable_id), constant(gi_1_1.label)))
|
gi_1_1.update(routing_rule: ds_eq(champ_value(drop_down_tdc.stable_id), constant('Deuxième choix')))
|
||||||
get :show, params: { procedure_id: procedure.id, id: gi_1_1.id }
|
get :show, params: { procedure_id: procedure.id, id: gi_1_1.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
it do
|
it do
|
||||||
expect(response).to have_http_status(:ok)
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(response.body).to include('Deuxième choix')
|
||||||
expect(response.body).not_to include('règle invalide')
|
expect(response.body).not_to include('règle invalide')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
describe Administrateurs::RoutingController, type: :controller do
|
|
||||||
include Logic
|
|
||||||
|
|
||||||
before { sign_in(procedure.administrateurs.first.user) }
|
|
||||||
|
|
||||||
describe '#update targeted champ' do
|
|
||||||
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }, { type: :text, libelle: 'Un champ texte' }]) }
|
|
||||||
let(:gi_2) { create(:groupe_instructeur, label: 'groupe 2', procedure: procedure) }
|
|
||||||
let(:drop_down_tdc) { procedure.draft_revision.types_de_champ.first }
|
|
||||||
let(:params) do
|
|
||||||
{
|
|
||||||
procedure_id: procedure.id,
|
|
||||||
targeted_champ: champ_value(drop_down_tdc.stable_id).to_json,
|
|
||||||
operator_name: operator_name,
|
|
||||||
value: empty.to_json,
|
|
||||||
groupe_instructeur_id: gi_2.id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with Eq operator' do
|
|
||||||
let(:operator_name) { Logic::Eq.name }
|
|
||||||
|
|
||||||
before do
|
|
||||||
post :update, params: params, format: :turbo_stream
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(drop_down_tdc.stable_id), empty))
|
|
||||||
end
|
|
||||||
|
|
||||||
context '#update value' do
|
|
||||||
let(:value_updated_params) { params.merge(value: constant('Lyon').to_json) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
post :update, params: value_updated_params, format: :turbo_stream
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'targeted champ changed' do
|
|
||||||
let(:last_tdc) { procedure.draft_revision.types_de_champ.last }
|
|
||||||
|
|
||||||
before do
|
|
||||||
targeted_champ_updated_params = value_updated_params.merge(targeted_champ: champ_value(last_tdc.stable_id).to_json)
|
|
||||||
post :update, params: targeted_champ_updated_params, format: :turbo_stream
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(last_tdc.stable_id), empty))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with NotEq operator' do
|
|
||||||
let(:operator_name) { Logic::NotEq.name }
|
|
||||||
|
|
||||||
before do
|
|
||||||
post :update, params: params, format: :turbo_stream
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(gi_2.reload.routing_rule).to eq(ds_not_eq(champ_value(drop_down_tdc.stable_id), empty))
|
|
||||||
end
|
|
||||||
|
|
||||||
context '#update value' do
|
|
||||||
let(:value_updated_params) { params.merge(value: constant('Lyon').to_json) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
post :update, params: value_updated_params, format: :turbo_stream
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(gi_2.reload.routing_rule).to eq(ds_not_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'targeted champ changed' do
|
|
||||||
let(:last_tdc) { procedure.draft_revision.types_de_champ.last }
|
|
||||||
|
|
||||||
before do
|
|
||||||
targeted_champ_updated_params = value_updated_params.merge(targeted_champ: champ_value(last_tdc.stable_id).to_json)
|
|
||||||
post :update, params: targeted_champ_updated_params, format: :turbo_stream
|
|
||||||
end
|
|
||||||
|
|
||||||
it do
|
|
||||||
expect(gi_2.reload.routing_rule).to eq(ds_not_eq(champ_value(last_tdc.stable_id), empty))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "#update_defaut_groupe_instructeur" do
|
|
||||||
let(:procedure) { create(:procedure) }
|
|
||||||
let(:gi_2) { create(:groupe_instructeur, label: 'groupe 2', procedure: procedure) }
|
|
||||||
let(:params) do
|
|
||||||
{
|
|
||||||
procedure_id: procedure.id,
|
|
||||||
defaut_groupe_instructeur_id: gi_2.id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
post :update_defaut_groupe_instructeur, params: params, format: :turbo_stream
|
|
||||||
procedure.reload
|
|
||||||
end
|
|
||||||
|
|
||||||
it { expect(procedure.defaut_groupe_instructeur.id).to eq(gi_2.id) }
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
describe Administrateurs::RoutingRulesController, type: :controller do
|
||||||
|
include Logic
|
||||||
|
|
||||||
|
before { sign_in(procedure.administrateurs.first.user) }
|
||||||
|
|
||||||
|
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :drop_down_list, libelle: 'Votre ville', options: ['Paris', 'Lyon', 'Marseille'] }, { type: :text, libelle: 'Un champ texte' }]) }
|
||||||
|
let(:gi_2) { create(:groupe_instructeur, label: 'groupe 2', procedure: procedure) }
|
||||||
|
let(:drop_down_tdc) { procedure.draft_revision.types_de_champ.first }
|
||||||
|
let(:default_params) do
|
||||||
|
{
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
groupe_instructeur_id: gi_2.id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#update' do
|
||||||
|
let(:value) { empty.to_json }
|
||||||
|
let(:targeted_champ) { champ_value(drop_down_tdc.stable_id).to_json }
|
||||||
|
|
||||||
|
before { post :update, params: params, format: :turbo_stream }
|
||||||
|
|
||||||
|
let(:params) { default_params.merge(groupe_instructeur: { condition_form: condition_form }) }
|
||||||
|
|
||||||
|
let(:condition_form) do
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
targeted_champ: targeted_champ,
|
||||||
|
operator_name: operator_name,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with Eq operator' do
|
||||||
|
let(:operator_name) { Logic::Eq.name }
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(drop_down_tdc.stable_id), empty))
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#update value' do
|
||||||
|
let(:value) { constant('Lyon').to_json }
|
||||||
|
|
||||||
|
before { post :update, params: params, format: :turbo_stream }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'targeted champ changed' do
|
||||||
|
let(:last_tdc) { procedure.draft_revision.types_de_champ.last }
|
||||||
|
let(:targeted_champ) { champ_value(last_tdc.stable_id).to_json }
|
||||||
|
let(:value) { empty.to_json }
|
||||||
|
|
||||||
|
before { post :update, params: params, format: :turbo_stream }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(last_tdc.stable_id), empty))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with NotEq operator' do
|
||||||
|
let(:operator_name) { Logic::NotEq.name }
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_not_eq(champ_value(drop_down_tdc.stable_id), empty))
|
||||||
|
end
|
||||||
|
|
||||||
|
context '#update value' do
|
||||||
|
let(:value) { constant('Lyon').to_json }
|
||||||
|
|
||||||
|
before { post :update, params: params, format: :turbo_stream }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_not_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'targeted champ changed' do
|
||||||
|
let(:last_tdc) { procedure.draft_revision.types_de_champ.last }
|
||||||
|
let(:targeted_champ) { champ_value(last_tdc.stable_id).to_json }
|
||||||
|
let(:value) { empty.to_json }
|
||||||
|
|
||||||
|
before { post :update, params: params, format: :turbo_stream }
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_not_eq(champ_value(last_tdc.stable_id), empty))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#add_row' do
|
||||||
|
before do
|
||||||
|
gi_2.update(routing_rule: ds_eq(champ_value(drop_down_tdc.stable_id), empty))
|
||||||
|
post :add_row, params: default_params, format: :turbo_stream
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_and([ds_eq(champ_value(drop_down_tdc.stable_id), empty), empty_operator(empty, empty)]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#delete_row' do
|
||||||
|
before do
|
||||||
|
gi_2.update(routing_rule: ds_and([ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')), empty_operator(empty, empty)]))
|
||||||
|
post :delete_row, params: params.merge(row_index: 1), format: :turbo_stream
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:params) { default_params.merge(groupe_instructeur: { condition_form: condition_form }) }
|
||||||
|
|
||||||
|
let(:condition_form) do
|
||||||
|
{
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
targeted_champ: champ_value(drop_down_tdc.stable_id).to_json,
|
||||||
|
operator_name: Logic::Eq.name,
|
||||||
|
value: constant('Lyon')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
targeted_champ: empty,
|
||||||
|
operator_name: Logic::Eq.name,
|
||||||
|
value: empty
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
expect(gi_2.reload.routing_rule).to eq(ds_eq(champ_value(drop_down_tdc.stable_id), constant('Lyon')))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#update_defaut_groupe_instructeur" do
|
||||||
|
let(:procedure) { create(:procedure) }
|
||||||
|
let(:gi_2) { create(:groupe_instructeur, label: 'groupe 2', procedure: procedure) }
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
procedure_id: procedure.id,
|
||||||
|
defaut_groupe_instructeur_id: gi_2.id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
post :update_defaut_groupe_instructeur, params: params, format: :turbo_stream
|
||||||
|
procedure.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(procedure.defaut_groupe_instructeur.id).to eq(gi_2.id) }
|
||||||
|
end
|
||||||
|
end
|
|
@ -39,14 +39,14 @@ describe 'The routing with rules', js: true do
|
||||||
expect(page).not_to have_text('à configurer')
|
expect(page).not_to have_text('à configurer')
|
||||||
|
|
||||||
click_on 'littéraire'
|
click_on 'littéraire'
|
||||||
expect(page).to have_select("targeted_champ", selected: "Spécialité")
|
expect(page).to have_select("groupe_instructeur[condition_form][rows][][targeted_champ]", selected: "Spécialité")
|
||||||
expect(page).to have_select("value", selected: "littéraire")
|
expect(page).to have_select("groupe_instructeur[condition_form][rows][][value]", selected: "littéraire")
|
||||||
|
|
||||||
click_on '3 groupes'
|
click_on '3 groupes'
|
||||||
click_on 'scientifique'
|
click_on 'scientifique'
|
||||||
|
|
||||||
expect(page).to have_select("targeted_champ", selected: "Spécialité")
|
expect(page).to have_select("groupe_instructeur[condition_form][rows][][targeted_champ]", selected: "Spécialité")
|
||||||
expect(page).to have_select("value", selected: "scientifique")
|
expect(page).to have_select("groupe_instructeur[condition_form][rows][][value]", selected: "scientifique")
|
||||||
end
|
end
|
||||||
|
|
||||||
scenario 'Routage avancé' do
|
scenario 'Routage avancé' do
|
||||||
|
@ -106,20 +106,20 @@ describe 'The routing with rules', js: true do
|
||||||
expect(page).to have_text("L’instructeur alain@gouv.fr a été affecté")
|
expect(page).to have_text("L’instructeur alain@gouv.fr a été affecté")
|
||||||
|
|
||||||
# add routing rules
|
# add routing rules
|
||||||
within('.target') { select('Spécialité') }
|
within('.target select') { select('Spécialité') }
|
||||||
within('.value') { select('scientifique') }
|
within('.value select') { select('scientifique') }
|
||||||
|
|
||||||
click_on '3 groupes'
|
click_on '3 groupes'
|
||||||
|
|
||||||
click_on 'littéraire'
|
click_on 'littéraire'
|
||||||
|
|
||||||
within('.target') { select('Spécialité') }
|
within('.target select') { select('Spécialité') }
|
||||||
within('.value') { select('scientifique') }
|
within('.value select') { select('scientifique') }
|
||||||
|
|
||||||
expect(page).to have_text('règle déjà attribuée à scientifique')
|
expect(page).to have_text('règle déjà attribuée à scientifique')
|
||||||
|
|
||||||
within('.target') { select('Spécialité') }
|
within('.target select') { select('Spécialité') }
|
||||||
within('.value') { select('littéraire') }
|
within('.value select') { select('littéraire') }
|
||||||
|
|
||||||
expect(page).not_to have_text('règle déjà attribuée à scientifique')
|
expect(page).not_to have_text('règle déjà attribuée à scientifique')
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue