From aca3e38859784c89a9c9f0fb7558622a479d06a3 Mon Sep 17 00:00:00 2001 From: mfo Date: Wed, 5 Jun 2024 17:25:10 +0200 Subject: [PATCH] feat(ProcedureRevision.ineligibilite_rules): add ineligibilite_rules management to procedure revision based on conditional logic --- .../ineligibilite_rules_component.rb | 34 +++ .../ineligibilite_rules_component.fr.yml | 6 + .../ineligibilite_rules_component.html.haml | 42 ++++ .../procedure/pending_republish_component.rb | 10 + .../pending_republish_component.fr.yml | 4 + .../pending_republish_component.html.haml | 3 + .../ineligibilite_rules_controller.rb | 74 ++++++ app/models/procedure_revision.rb | 12 + .../_update.turbo_stream.haml | 7 + .../add_row.turbo_stream.haml | 1 + .../change_targeted_champ.turbo_stream.haml | 1 + .../delete_row.turbo_stream.haml | 1 + .../destroy.turbo_stream.haml | 1 + .../ineligibilite_rules/edit.html.haml | 28 +++ .../update.turbo_stream.haml | 1 + config/env.example.optional | 3 + config/initializers/02_urls.rb | 1 + config/routes.rb | 8 + .../ineligibilite_rules_component_spec.rb | 64 +++++ .../pending_republish_component_spec.rb | 14 ++ .../ineligibilite_rules_controller_spec.rb | 231 ++++++++++++++++++ .../procedure_ineligibilite_spec.rb | 45 ++++ 22 files changed, 591 insertions(+) create mode 100644 app/components/conditions/ineligibilite_rules_component.rb create mode 100644 app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.fr.yml create mode 100644 app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.html.haml create mode 100644 app/components/procedure/pending_republish_component.rb create mode 100644 app/components/procedure/pending_republish_component/pending_republish_component.fr.yml create mode 100644 app/components/procedure/pending_republish_component/pending_republish_component.html.haml create mode 100644 app/controllers/administrateurs/ineligibilite_rules_controller.rb create mode 100644 app/views/administrateurs/ineligibilite_rules/_update.turbo_stream.haml create mode 100644 app/views/administrateurs/ineligibilite_rules/add_row.turbo_stream.haml create mode 100644 app/views/administrateurs/ineligibilite_rules/change_targeted_champ.turbo_stream.haml create mode 100644 app/views/administrateurs/ineligibilite_rules/delete_row.turbo_stream.haml create mode 100644 app/views/administrateurs/ineligibilite_rules/destroy.turbo_stream.haml create mode 100644 app/views/administrateurs/ineligibilite_rules/edit.html.haml create mode 100644 app/views/administrateurs/ineligibilite_rules/update.turbo_stream.haml create mode 100644 spec/components/conditions/ineligibilite_rules_component_spec.rb create mode 100644 spec/components/procedures/pending_republish_component_spec.rb create mode 100644 spec/controllers/administrateurs/ineligibilite_rules_controller_spec.rb create mode 100644 spec/system/administrateurs/procedure_ineligibilite_spec.rb diff --git a/app/components/conditions/ineligibilite_rules_component.rb b/app/components/conditions/ineligibilite_rules_component.rb new file mode 100644 index 000000000..a12ab262e --- /dev/null +++ b/app/components/conditions/ineligibilite_rules_component.rb @@ -0,0 +1,34 @@ +class Conditions::IneligibiliteRulesComponent < Conditions::ConditionsComponent + include Logic + + def initialize(draft_revision:) + @draft_revision = draft_revision + @published_revision = draft_revision.procedure.published_revision + @condition = draft_revision.ineligibilite_rules + @source_tdcs = draft_revision.types_de_champ_for(scope: :public) + end + + def pending_changes? + return false if !@published_revision + + !@published_revision.compare_ineligibilite_rules(@draft_revision).empty? + end + + private + + def input_prefix + 'procedure_revision[condition_form]' + end + + def input_id_for(name, row_index) + "#{@draft_revision.id}-#{name}-#{row_index}" + end + + def delete_condition_path(row_index) + delete_row_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id, revision_id: @draft_revision.id, row_index:) + end + + def add_condition_path + add_row_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id, revision_id: @draft_revision.id) + end +end diff --git a/app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.fr.yml b/app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.fr.yml new file mode 100644 index 000000000..b646c3019 --- /dev/null +++ b/app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.fr.yml @@ -0,0 +1,6 @@ +--- +fr: + display_if: Bloquer si + select: Sélectionner + add_condition: Ajouter une règle d’inéligibilité + remove_a_row: Supprimer une règle diff --git a/app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.html.haml b/app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.html.haml new file mode 100644 index 000000000..547a2ad85 --- /dev/null +++ b/app/components/conditions/ineligibilite_rules_component/ineligibilite_rules_component.html.haml @@ -0,0 +1,42 @@ +%div{ id: dom_id(@draft_revision, :ineligibilite_rules) } + = render Procedure::PendingRepublishComponent.new(procedure: @draft_revision.procedure, render_if: pending_changes?) + = render Conditions::ConditionsErrorsComponent.new(conditions: condition_per_row, source_tdcs: @source_tdcs) + %fieldset.fr-fieldset + %legend.fr-mx-1w.fr-label.fr-py-0.fr-mb-1w.fr-mt-2w + Règles d’inéligibilité + %span.fr-hint-text Vous pouvez utiliser 1 ou plusieurs critère pour bloquer le dépot + .fr-fieldset__element + = form_tag admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id), method: :patch, data: { turbo: true, controller: 'autosave' }, class: 'form width-100' do + .conditionnel.width-100 + %table.condition-table + %thead + %tr + %th.fr-pt-0.far-left + %th.fr-pt-0.target Champ Cible + %th.fr-pt-0.operator Opérateur + %th.fr-pt-0.value Valeur + %th.fr-pt-0.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, operator_name) + %td.delete-column= delete_condition_tag(row_index) + %tfoot + %tr + %td.text-right{ colspan: 5 }= add_condition_tag + + + + = form_for(@draft_revision, url: change_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id)) do |f| + .fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :ineligibilite_message, input_type: :text_area, opts: {rows: 5}) + .fr-fieldset__element + .fr-toggle + = f.check_box :ineligibilite_enabled, class: 'fr-toggle__input', data: @opt + = f.label :ineligibilite_enabled, "Inéligibilité des dossiers", data: { 'fr-checked-label': "Actif", 'fr-unchecked-label': "Inactif" }, class: 'fr-toggle__label' + %p.fr-hint-text Passer l’intérrupteur sur activé pour que les critères d’inéligibilité configurés s'appliquent + + + = render Procedure::FixedFooterComponent.new(procedure: @draft_revision.procedure, form: f, extra_class_names: 'fr-col-offset-md-2 fr-col-md-8') diff --git a/app/components/procedure/pending_republish_component.rb b/app/components/procedure/pending_republish_component.rb new file mode 100644 index 000000000..181eb6f5c --- /dev/null +++ b/app/components/procedure/pending_republish_component.rb @@ -0,0 +1,10 @@ +class Procedure::PendingRepublishComponent < ApplicationComponent + def initialize(procedure:, render_if:) + @procedure = procedure + @render_if = render_if + end + + def render? + @render_if + end +end diff --git a/app/components/procedure/pending_republish_component/pending_republish_component.fr.yml b/app/components/procedure/pending_republish_component/pending_republish_component.fr.yml new file mode 100644 index 000000000..eb941cdba --- /dev/null +++ b/app/components/procedure/pending_republish_component/pending_republish_component.fr.yml @@ -0,0 +1,4 @@ +--- +fr: + pending_republish_html: | + Ces modifications ne seront appliquées qu'à la prochaine publication. Vous pouvez vérifier puis publier les modifications sur l'écran de gestion de la démarche \ No newline at end of file diff --git a/app/components/procedure/pending_republish_component/pending_republish_component.html.haml b/app/components/procedure/pending_republish_component/pending_republish_component.html.haml new file mode 100644 index 000000000..eab7f62fc --- /dev/null +++ b/app/components/procedure/pending_republish_component/pending_republish_component.html.haml @@ -0,0 +1,3 @@ += render Dsfr::AlertComponent.new(state: :warning) do |c| + - c.with_body do + = t('.pending_republish_html', href: admin_procedure_path(@procedure.id)) diff --git a/app/controllers/administrateurs/ineligibilite_rules_controller.rb b/app/controllers/administrateurs/ineligibilite_rules_controller.rb new file mode 100644 index 000000000..41d6865f9 --- /dev/null +++ b/app/controllers/administrateurs/ineligibilite_rules_controller.rb @@ -0,0 +1,74 @@ +module Administrateurs + class IneligibiliteRulesController < AdministrateurController + before_action :retrieve_procedure + + def edit + end + + def change + if draft_revision.update(procedure_revision_params) + redirect_to edit_admin_procedure_ineligibilite_rules_path(@procedure) + else + flash[:alert] = draft_revision.errors.full_messages + render :edit + end + end + + def add_row + condition = Logic.add_empty_condition_to(draft_revision.ineligibilite_rules) + draft_revision.update!(ineligibilite_rules: condition) + @ineligibilite_rules_component = build_ineligibilite_rules_component + end + + def delete_row + condition = condition_form.delete_row(row_index).to_condition + draft_revision.update!(ineligibilite_rules: condition) + + @ineligibilite_rules_component = build_ineligibilite_rules_component + end + + def update + condition = condition_form.to_condition + draft_revision.update!(ineligibilite_rules: condition) + + @ineligibilite_rules_component = build_ineligibilite_rules_component + end + + def change_targeted_champ + condition = condition_form.change_champ(row_index).to_condition + draft_revision.update!(ineligibilite_rules: condition) + @ineligibilite_rules_component = build_ineligibilite_rules_component + end + + private + + def build_ineligibilite_rules_component + Conditions::IneligibiliteRulesComponent.new(draft_revision: draft_revision) + end + + def draft_revision + @procedure.draft_revision + end + + def condition_form + ConditionForm.new(ineligibilite_rules_params.merge(source_tdcs: draft_revision.types_de_champ_for(scope: :public))) + end + + def ineligibilite_rules_params + params + .require(:procedure_revision) + .require(:condition_form) + .permit(:top_operator_name, rows: [:targeted_champ, :operator_name, :value]) + end + + def row_index + params[:row_index].to_i + end + + def procedure_revision_params + params + .require(:procedure_revision) + .permit(:ineligibilite_message, :ineligibilite_enabled) + end + end +end diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index 157b4dd67..2b56ecf80 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -1,4 +1,5 @@ class ProcedureRevision < ApplicationRecord + include Logic self.implicit_order_column = :created_at belongs_to :procedure, -> { with_discarded }, inverse_of: :revisions, optional: false belongs_to :dossier_submitted_message, inverse_of: :revisions, optional: true, dependent: :destroy @@ -17,8 +18,19 @@ class ProcedureRevision < ApplicationRecord scope :ordered, -> { order(:created_at) } + validates :ineligibilite_message, presence: true, if: -> { ineligibilite_enabled? } + delegate :path, to: :procedure, prefix: true + validate :ineligibilite_rules_are_valid?, + on: [:ineligibilite_rules_editor, :publication] + validates :ineligibilite_message, + presence: true, + if: -> { ineligibilite_enabled? }, + on: [:ineligibilite_rules_editor, :publication] + + serialize :ineligibilite_rules, LogicSerializer + def build_champs_public # reload: it can be out of sync in test if some tdcs are added wihtout using add_tdc types_de_champ_public.reload.map(&:build_champ) diff --git a/app/views/administrateurs/ineligibilite_rules/_update.turbo_stream.haml b/app/views/administrateurs/ineligibilite_rules/_update.turbo_stream.haml new file mode 100644 index 000000000..a0ace0eca --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/_update.turbo_stream.haml @@ -0,0 +1,7 @@ +- rendered = render @ineligibilite_rules_component + +- if rendered.present? + = turbo_stream.replace dom_id(@procedure.draft_revision, :ineligibilite_rules) do + - rendered +- else + = turbo_stream.remove dom_id(@procedure.draft_revision, :ineligibilite_rules) diff --git a/app/views/administrateurs/ineligibilite_rules/add_row.turbo_stream.haml b/app/views/administrateurs/ineligibilite_rules/add_row.turbo_stream.haml new file mode 100644 index 000000000..8f9900e50 --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/add_row.turbo_stream.haml @@ -0,0 +1 @@ += render partial: 'update' diff --git a/app/views/administrateurs/ineligibilite_rules/change_targeted_champ.turbo_stream.haml b/app/views/administrateurs/ineligibilite_rules/change_targeted_champ.turbo_stream.haml new file mode 100644 index 000000000..8f9900e50 --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/change_targeted_champ.turbo_stream.haml @@ -0,0 +1 @@ += render partial: 'update' diff --git a/app/views/administrateurs/ineligibilite_rules/delete_row.turbo_stream.haml b/app/views/administrateurs/ineligibilite_rules/delete_row.turbo_stream.haml new file mode 100644 index 000000000..8f9900e50 --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/delete_row.turbo_stream.haml @@ -0,0 +1 @@ += render partial: 'update' diff --git a/app/views/administrateurs/ineligibilite_rules/destroy.turbo_stream.haml b/app/views/administrateurs/ineligibilite_rules/destroy.turbo_stream.haml new file mode 100644 index 000000000..8f9900e50 --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/destroy.turbo_stream.haml @@ -0,0 +1 @@ += render partial: 'update' diff --git a/app/views/administrateurs/ineligibilite_rules/edit.html.haml b/app/views/administrateurs/ineligibilite_rules/edit.html.haml new file mode 100644 index 000000000..a76a30468 --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/edit.html.haml @@ -0,0 +1,28 @@ += render partial: 'administrateurs/breadcrumbs', + locals: { steps: [['Démarches', admin_procedures_path], + [@procedure.libelle.truncate_words(10), admin_procedure_path(@procedure)], + ['Inéligibilité des dossiers']] } + + +.fr-container + .fr-grid-row + .fr-col-12.fr-col-offset-md-2.fr-col-md-8 + %h1.fr-h1 Inéligibilité des dossiers + + = render Dsfr::AlertComponent.new(title: nil, size: :sm, state: :info, heading_level: 'h2', extra_class_names: 'fr-my-2w') do |c| + - c.with_body do + %p + Les dossiers répondant à vos critères d’inéligibilité ne pourront pas être déposés. Plus d’informations sur l’inéligibilité des dossiers dans la + = link_to('doc', ELIGIBILITE_URL, title: "Document sur l’inéligibilité des dossiers", **external_link_attributes) + + - if !@procedure.draft_revision.conditionable_types_de_champ.present? + %p.fr-mt-2w.fr-mb-2w + Pour configurer l’inéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les critères d’inéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire : + %ul + - Logic::ChampValue::MANAGED_TYPE_DE_CHAMP.values.each do + %li= "« #{t(_1, scope: [:activerecord, :attributes, :type_de_champ, :type_champs])} »" + %p.fr-mt-2w + = link_to 'Ajouter un champ supportant les critères d’inéligibilité', champs_admin_procedure_path(@procedure), class: 'fr-link fr-icon-arrow-right-line fr-link--icon-right' + = render Procedure::FixedFooterComponent.new(procedure: @procedure) + - else + = render Conditions::IneligibiliteRulesComponent.new(draft_revision: @procedure.draft_revision) diff --git a/app/views/administrateurs/ineligibilite_rules/update.turbo_stream.haml b/app/views/administrateurs/ineligibilite_rules/update.turbo_stream.haml new file mode 100644 index 000000000..8f9900e50 --- /dev/null +++ b/app/views/administrateurs/ineligibilite_rules/update.turbo_stream.haml @@ -0,0 +1 @@ += render partial: 'update' diff --git a/config/env.example.optional b/config/env.example.optional index b9cc395e9..79710f04e 100644 --- a/config/env.example.optional +++ b/config/env.example.optional @@ -61,6 +61,9 @@ DS_ENV="staging" # Instance customization: URL of the Routage documentation # ROUTAGE_URL="" # +# Instance customization: URL of the EligibiliteDossier documentation +# ELIGIBILITE_URL="" +# # Instance customization: URL of the accessibility statement # ACCESSIBILITE_URL="" diff --git a/config/initializers/02_urls.rb b/config/initializers/02_urls.rb index d6e031dae..c29ea1d5d 100644 --- a/config/initializers/02_urls.rb +++ b/config/initializers/02_urls.rb @@ -37,6 +37,7 @@ CGU_URL = ENV.fetch("CGU_URL", [DOC_URL, "cgu"].join("/")) MENTIONS_LEGALES_URL = ENV.fetch("MENTIONS_LEGALES_URL", "/mentions-legales") ACCESSIBILITE_URL = ENV.fetch("ACCESSIBILITE_URL", "/declaration-accessibilite") ROUTAGE_URL = ENV.fetch("ROUTAGE_URL", [DOC_URL, "/pour-aller-plus-loin/routage"].join("/")) +ELIGIBILITE_URL = ENV.fetch("ELIGIBILITE_URL", [DOC_URL, "/pour-aller-plus-loin/eligibilite-des-dossiers"].join("/")) API_DOC_URL = [DOC_URL, "api-graphql"].join("/") WEBHOOK_DOC_URL = [DOC_URL, "pour-aller-plus-loin", "webhook"].join("/") WEBHOOK_ALTERNATIVE_DOC_URL = [DOC_URL, "api-graphql", "cas-dusages-exemple-dimplementation", "synchroniser-les-dossiers-modifies-sur-ma-demarche"].join("/") diff --git a/config/routes.rb b/config/routes.rb index c4973d1ae..38bd26cc9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -607,6 +607,14 @@ Rails.application.routes.draw do delete :delete_row, on: :member end + resource :ineligibilite_rules, only: [:edit, :update, :destroy], param: :revision_id do + patch :change_targeted_champ, on: :member + patch :update_all_rows, on: :member + patch :add_row, on: :member + delete :delete_row, on: :member + patch :change + end + patch :update_defaut_groupe_instructeur, controller: 'routing_rules', as: :update_defaut_groupe_instructeur put 'clone' diff --git a/spec/components/conditions/ineligibilite_rules_component_spec.rb b/spec/components/conditions/ineligibilite_rules_component_spec.rb new file mode 100644 index 000000000..c678c5ace --- /dev/null +++ b/spec/components/conditions/ineligibilite_rules_component_spec.rb @@ -0,0 +1,64 @@ +describe Conditions::IneligibiliteRulesComponent, type: :component do + include Logic + let(:procedure) { create(:procedure) } + let(:component) { described_class.new(draft_revision: procedure.draft_revision) } + + describe 'render' do + let(:ineligibilite_message) { 'ok' } + let(:ineligibilite_enabled) { true } + before do + procedure.draft_revision.update(ineligibilite_rules:, ineligibilite_message:, ineligibilite_enabled:) + end + context 'when ineligibilite_rules are valid' do + let(:ineligibilite_rules) { ds_eq(constant(true), constant(true)) } + it 'does not render error' do + render_inline(component) + expect(page).not_to have_selector('.errors-summary') + end + end + context 'when ineligibilite_rules are invalid' do + let(:ineligibilite_rules) { ds_eq(constant(true), constant(1)) } + it 'does not render error' do + render_inline(component) + expect(page).to have_selector('.errors-summary') + end + end + end + + describe '#pending_changes' do + context 'when procedure is published' do + it 'detect changes when setup changes' do + expect(component.pending_changes?).to be_falsey + + procedure.draft_revision.ineligibilite_message = 'changed' + expect(component.pending_changes?).to be_falsey + + procedure.reload + procedure.draft_revision.ineligibilite_enabled = true + expect(component.pending_changes?).to be_falsey + + procedure.reload + procedure.draft_revision.ineligibilite_rules = {} + expect(component.pending_changes?).to be_falsey + end + end + + context 'when procedure is published' do + let(:procedure) { create(:procedure, :published) } + it 'detect changes when setup changes' do + expect(component.pending_changes?).to be_falsey + + procedure.draft_revision.ineligibilite_message = 'changed' + expect(component.pending_changes?).to be_truthy + + procedure.reload + procedure.draft_revision.ineligibilite_enabled = true + expect(component.pending_changes?).to be_truthy + + procedure.reload + procedure.draft_revision.ineligibilite_rules = {} + expect(component.pending_changes?).to be_truthy + end + end + end +end diff --git a/spec/components/procedures/pending_republish_component_spec.rb b/spec/components/procedures/pending_republish_component_spec.rb new file mode 100644 index 000000000..a5e301a20 --- /dev/null +++ b/spec/components/procedures/pending_republish_component_spec.rb @@ -0,0 +1,14 @@ +describe Procedure::PendingRepublishComponent, type: :component do + subject { render_inline(described_class.new(render_if:, procedure: build(:procedure, id: 1))) } + let(:page) { subject } + describe 'render_if' do + context 'when false' do + let(:render_if) { false } + it { expect(page).not_to have_text('Ces modifications ne seront appliquées') } + end + context 'when true' do + let(:render_if) { true } + it { expect(page).to have_text('Ces modifications ne seront appliquées') } + end + end +end diff --git a/spec/controllers/administrateurs/ineligibilite_rules_controller_spec.rb b/spec/controllers/administrateurs/ineligibilite_rules_controller_spec.rb new file mode 100644 index 000000000..5c8f94628 --- /dev/null +++ b/spec/controllers/administrateurs/ineligibilite_rules_controller_spec.rb @@ -0,0 +1,231 @@ +describe Administrateurs::IneligibiliteRulesController, type: :controller do + include Logic + let(:user) { create(:user) } + let(:admin) { create(:administrateur, user: create(:user)) } + let(:procedure) { create(:procedure, administrateurs: [admin], types_de_champ_public:) } + let(:types_de_champ_public) { [] } + + describe 'condition management' do + before { sign_in(admin.user) } + + let(:default_params) do + { + procedure_id: procedure.id, + revision_id: procedure.draft_revision.id + } + end + + describe '#add_row' do + subject { post :add_row, params: default_params, format: :turbo_stream } + + context 'without any row' do + it 'creates an empty condition' do + expect { subject }.to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(nil) + .to(empty_operator(empty, empty)) + end + end + + context 'with row' do + before do + procedure.draft_revision.ineligibilite_rules = empty_operator(empty, empty) + procedure.draft_revision.save! + end + + it 'add one more creates an empty condition' do + expect { subject }.to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(empty_operator(empty, empty)) + .to(ds_and([ + empty_operator(empty, empty), + empty_operator(empty, empty) + ])) + end + end + end + + describe 'delete_row' do + let(:condition_form) do + { + top_operator_name: Logic::And.name, + rows: [ + { + targeted_champ: empty.to_json, + operator_name: Logic::EmptyOperator, + value: empty.to_json + }, + { + targeted_champ: empty.to_json, + operator_name: Logic::EmptyOperator, + value: empty.to_json + } + ] + } + end + let(:initial_condition) do + ds_and([ + empty_operator(empty, empty), + empty_operator(empty, empty) + ]) + end + + subject { delete :delete_row, params: default_params.merge(row_index: 0, procedure_revision: { condition_form: }), format: :turbo_stream } + it 'remove condition' do + procedure.draft_revision.update(ineligibilite_rules: initial_condition) + + expect { subject } + .to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(initial_condition) + .to(empty_operator(empty, empty)) + end + end + + context 'simple tdc' do + let(:types_de_champ_public) { [{ type: :yes_no }] } + let(:yes_no_tdc) { procedure.draft_revision.types_de_champ_for(scope: :public).first } + let(:targeted_champ) { champ_value(yes_no_tdc.stable_id).to_json } + + describe '#change_targeted_champ' do + let(:condition_form) do + { + rows: [ + { + targeted_champ: targeted_champ, + operator_name: Logic::Eq.name, + value: constant(true).to_json + } + ] + } + end + subject { patch :change_targeted_champ, params: default_params.merge(procedure_revision: { condition_form: }), format: :turbo_stream } + it 'update condition' do + expect { subject }.to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(nil) + .to(ds_eq(champ_value(yes_no_tdc.stable_id), constant(true))) + end + end + + describe '#update' do + let(:value) { constant(true).to_json } + let(:operator_name) { Logic::Eq.name } + let(:condition_form) do + { + rows: [ + { + targeted_champ: targeted_champ, + operator_name: operator_name, + value: value + } + ] + } + end + subject { patch :update, params: default_params.merge(procedure_revision: { condition_form: condition_form }), format: :turbo_stream } + it 'updates condition' do + expect { subject }.to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(nil) + .to(ds_eq(champ_value(yes_no_tdc.stable_id), constant(true))) + end + end + end + + context 'repetition tdc' do + let(:types_de_champ_public) { [{ type: :repetition, children: [{ type: :yes_no }] }] } + let(:yes_no_tdc) { procedure.draft_revision.types_de_champ_for(scope: :public).find { _1.type_champ == 'yes_no' } } + let(:targeted_champ) { champ_value(yes_no_tdc.stable_id).to_json } + let(:condition_form) do + { + rows: [ + { + targeted_champ: targeted_champ, + operator_name: Logic::Eq.name, + value: constant(true).to_json + } + ] + } + end + subject { patch :change_targeted_champ, params: default_params.merge(procedure_revision: { condition_form: }), format: :turbo_stream } + describe "#update" do + it 'update condition' do + expect { subject }.to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(nil) + .to(ds_eq(champ_value(yes_no_tdc.stable_id), constant(true))) + end + end + + describe '#change_targeted_champ' do + let(:condition_form) do + { + rows: [ + { + targeted_champ: targeted_champ, + operator_name: Logic::Eq.name, + value: constant(true).to_json + } + ] + } + end + subject { patch :change_targeted_champ, params: default_params.merge(procedure_revision: { condition_form: }), format: :turbo_stream } + it 'update condition' do + expect { subject }.to change { procedure.draft_revision.reload.ineligibilite_rules } + .from(nil) + .to(ds_eq(champ_value(yes_no_tdc.stable_id), constant(true))) + end + end + end + end + + describe '#edit' do + subject { get :edit, params: { procedure_id: procedure.id } } + + context 'when user is not signed in' do + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'when user is signed in but not admin of procedure' do + before { sign_in(user) } + it { is_expected.to redirect_to(new_user_session_path) } + end + + context 'when user is signed as admin' do + before do + sign_in(admin.user) + subject + end + + it { is_expected.to have_http_status(200) } + + context 'rendered without tdc' do + let(:types_de_champ_public) { [] } + render_views + + it { expect(response.body).to have_link("Ajouter un champ supportant les critères d’inéligibilité") } + end + + context 'rendered with tdc' do + let(:types_de_champ_public) { [{ type: :yes_no }] } + render_views + + it { expect(response.body).not_to have_link("Ajouter un champ supportant les critères d’inéligibilité") } + end + end + end + + describe 'change' do + let(:params) do + { + procedure_id: procedure.id, + procedure_revision: { + ineligibilite_message: 'panpan', + ineligibilite_enabled: '1' + } + } + end + before { sign_in(admin.user) } + it 'works' do + patch :change, params: params + draft_revision = procedure.reload.draft_revision + expect(draft_revision.ineligibilite_message).to eq('panpan') + expect(draft_revision.ineligibilite_enabled).to eq(true) + expect(response).to redirect_to(edit_admin_procedure_ineligibilite_rules_path(procedure)) + end + end +end diff --git a/spec/system/administrateurs/procedure_ineligibilite_spec.rb b/spec/system/administrateurs/procedure_ineligibilite_spec.rb new file mode 100644 index 000000000..9db80cf59 --- /dev/null +++ b/spec/system/administrateurs/procedure_ineligibilite_spec.rb @@ -0,0 +1,45 @@ +describe 'Administrateurs can edit procedures', js: true do + include Logic + + let(:procedure) { create(:procedure, administrateurs: [create(:administrateur)]) } + before do + login_as procedure.administrateurs.first.user, scope: :user + end + + scenario 'setup eligibilite' do + # explain no champ compatible + visit admin_procedure_path(procedure) + expect(page).to have_content("Champs à configurer") + + # explain which champs are compatible + visit edit_admin_procedure_ineligibilite_rules_path(procedure) + expect(page).to have_content("Inéligibilité des dossiers") + expect(page).to have_content("Pour configurer l’inéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les critères d’inéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire : ") + click_on "Ajouter un champ supportant les critères d’inéligibilité" + + # setup a compatible champ + expect(page).to have_content('Champs du formulaire') + click_on 'Ajouter un champ' + select "Oui/Non" + fill_in "Libellé du champ", with: "Un champ oui non" + click_on "Revenir à l'écran de gestion" + procedure.reload + first_tdc = procedure.draft_revision.types_de_champ.first + # back to procedure dashboard, explain you can set it up now + expect(page).to have_content('À configurer') + visit edit_admin_procedure_ineligibilite_rules_path(procedure) + + # setup rules and stuffs + expect(page).to have_content("Inéligibilité des dossiers") + fill_in "Message d’inéligibilité", with: "vous n'etes pas eligible" + find('label', text: 'Inéligibilité des dossiers').click + click_on "Ajouter une règle d’inéligibilité" + all('select').first.select 'Un champ oui non' + click_on 'Enregistrer' + + # rules are setup + wait_until { procedure.reload.draft_revision.ineligibilite_enabled == true } + expect(procedure.draft_revision.ineligibilite_message).to eq("vous n'etes pas eligible") + expect(procedure.draft_revision.ineligibilite_rules).to eq(ds_eq(champ_value(first_tdc.stable_id), constant(true))) + end +end