From 120b59301578c20fa4c48a7ab1dd160874488ed2 Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Thu, 23 Jun 2022 12:12:17 +0200 Subject: [PATCH 1/3] feat(dossier): toggle conditional champs visibility --- app/controllers/users/dossiers_controller.rb | 2 +- app/models/champ.rb | 16 ++++++++++++++ .../editable_champs/_editable_champ.html.haml | 2 +- .../update_brouillon.turbo_stream.haml | 5 +++++ spec/system/users/brouillon_spec.rb | 22 +++++++++++++++++++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 app/views/users/dossiers/update_brouillon.turbo_stream.haml diff --git a/app/controllers/users/dossiers_controller.rb b/app/controllers/users/dossiers_controller.rb index 4d6d6821b..72e2fb8f3 100644 --- a/app/controllers/users/dossiers_controller.rb +++ b/app/controllers/users/dossiers_controller.rb @@ -172,7 +172,7 @@ module Users respond_to do |format| format.html { render :brouillon } - format.turbo_stream + format.turbo_stream { render layout: false } end end diff --git a/app/models/champ.rb b/app/models/champ.rb index ec769dac8..d2127d27a 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -180,8 +180,24 @@ class Champ < ApplicationRecord raise NotImplemented.new(:fetch_external_data) end + def conditional? + type_de_champ.condition.present? + end + + def visible? + if conditional? + type_de_champ.condition.compute(champs_for_condition) + else + true + end + end + private + def champs_for_condition + private? ? dossier.champs_private : dossier.champs + end + def html_id "#{stable_id}-#{id}" end diff --git a/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml b/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml index d33042f15..534e654b9 100644 --- a/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml +++ b/app/views/shared/dossiers/editable_champs/_editable_champ.html.haml @@ -1,4 +1,4 @@ -.editable-champ{ class: "editable-champ-#{champ.type_champ}", id: champ.input_group_id, data: autosave_available?(champ) ? { controller: 'autosave' } : {} } +.editable-champ{ class: "editable-champ-#{champ.type_champ} #{champ.visible? ? '' : 'hidden'}", id: champ.input_group_id, data: autosave_available?(champ) ? { controller: 'autosave' } : {} } - if champ.repetition? %h3.header-subsection= champ.libelle - if champ.description.present? diff --git a/app/views/users/dossiers/update_brouillon.turbo_stream.haml b/app/views/users/dossiers/update_brouillon.turbo_stream.haml new file mode 100644 index 000000000..59eb62452 --- /dev/null +++ b/app/views/users/dossiers/update_brouillon.turbo_stream.haml @@ -0,0 +1,5 @@ +- @dossier.champs.filter(&:conditional?).each do |champ| + - if champ.visible? + = turbo_stream.show champ.input_group_id + - else + = turbo_stream.hide champ.input_group_id diff --git a/spec/system/users/brouillon_spec.rb b/spec/system/users/brouillon_spec.rb index aeb3ecc25..da99b2deb 100644 --- a/spec/system/users/brouillon_spec.rb +++ b/spec/system/users/brouillon_spec.rb @@ -271,6 +271,28 @@ describe 'The user' do expect(page).to have_text('file.pdf') end + context 'with condition' do + include Logic + + let(:procedure) { create(:procedure, :published, :for_individual, :with_yes_no, :with_type_de_champ) } + let(:revision) { procedure.published_revision } + let(:yes_no_type_de_champ) { revision.types_de_champ.first } + let(:type_de_champ) { revision.types_de_champ.last } + let(:condition) { ds_eq(champ_value(yes_no_type_de_champ.stable_id), constant(true)) } + + before { type_de_champ.update(condition: condition) } + + scenario 'fill a dossier', js: true do + log_in(user, procedure) + + fill_individual + + expect(page).to have_field(type_de_champ.libelle, with: '', visible: false) + choose('Oui') + expect(page).to have_field(type_de_champ.libelle, with: '', visible: true) + end + end + context 'draft autosave' do scenario 'autosave a draft', js: true do log_in(user, simple_procedure) From 0f9d8b6a395ccddb105a1d221673bca411a7b0ff Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Mon, 27 Jun 2022 11:55:56 +0200 Subject: [PATCH 2/3] feat(dossier): exclude conditionally invisible fields from mandatory check --- app/models/champ.rb | 4 ++-- app/models/champs/piece_justificative_champ.rb | 4 ++-- app/models/champs/siret_champ.rb | 4 ++-- app/models/champs/titre_identite_champ.rb | 4 ++-- app/models/dossier.rb | 2 +- spec/models/champ_shared_example.rb | 14 +++++++------- .../champs/linked_drop_down_list_champ_spec.rb | 10 +++++----- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/models/champ.rb b/app/models/champ.rb index d2127d27a..c69907f8a 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -89,8 +89,8 @@ class Champ < ApplicationRecord @sections ||= dossier&.sections_for(self) end - def mandatory_and_blank? - mandatory? && blank? + def mandatory_blank_and_visible? + mandatory? && blank? && visible? end def blank? diff --git a/app/models/champs/piece_justificative_champ.rb b/app/models/champs/piece_justificative_champ.rb index ecd534fd7..25b2afff8 100644 --- a/app/models/champs/piece_justificative_champ.rb +++ b/app/models/champs/piece_justificative_champ.rb @@ -38,8 +38,8 @@ class Champs::PieceJustificativeChamp < Champ # We don’t know how to search inside documents yet end - def mandatory_and_blank? - mandatory? && !piece_justificative_file.attached? + def mandatory_blank_and_visible? + mandatory? && !piece_justificative_file.attached? && visible? end def for_export diff --git a/app/models/champs/siret_champ.rb b/app/models/champs/siret_champ.rb index 8b7347235..7641978d4 100644 --- a/app/models/champs/siret_champ.rb +++ b/app/models/champs/siret_champ.rb @@ -24,7 +24,7 @@ class Champs::SiretChamp < Champ etablissement.present? ? etablissement.search_terms : [value] end - def mandatory_and_blank? - mandatory? && Siret.new(siret: value).invalid? + def mandatory_blank_and_visible? + mandatory? && Siret.new(siret: value).invalid? && visible? end end diff --git a/app/models/champs/titre_identite_champ.rb b/app/models/champs/titre_identite_champ.rb index 7fa81233d..22d679aee 100644 --- a/app/models/champs/titre_identite_champ.rb +++ b/app/models/champs/titre_identite_champ.rb @@ -32,8 +32,8 @@ class Champs::TitreIdentiteChamp < Champ # We don’t know how to search inside documents yet end - def mandatory_and_blank? - mandatory? && !piece_justificative_file.attached? + def mandatory_blank_and_visible? + mandatory? && !piece_justificative_file.attached? && visible? end def for_export diff --git a/app/models/dossier.rb b/app/models/dossier.rb index 1c247b523..106924547 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -1077,7 +1077,7 @@ class Dossier < ApplicationRecord def check_mandatory_champs (champs + champs.filter(&:repetition?).flat_map(&:champs)) - .filter(&:mandatory_and_blank?) + .filter(&:mandatory_blank_and_visible?) .map do |champ| "Le champ #{champ.libelle.truncate(200)} doit être rempli." end diff --git a/spec/models/champ_shared_example.rb b/spec/models/champ_shared_example.rb index beebf0daf..852767dec 100644 --- a/spec/models/champ_shared_example.rb +++ b/spec/models/champ_shared_example.rb @@ -1,26 +1,26 @@ shared_examples 'champ_spec' do - describe 'mandatory_and_blank?' do + describe 'mandatory_blank_and_visible?' do let(:type_de_champ) { build(:type_de_champ, mandatory: mandatory) } let(:champ) { build(:champ, type_de_champ: type_de_champ, value: value) } let(:value) { '' } let(:mandatory) { true } context 'when mandatory and blank' do - it { expect(champ.mandatory_and_blank?).to be(true) } + it { expect(champ.mandatory_blank_and_visible?).to be(true) } end context 'when carte mandatory and blank' do let(:type_de_champ) { build(:type_de_champ_carte, mandatory: mandatory) } let(:champ) { build(:champ_carte, type_de_champ: type_de_champ, value: value) } let(:value) { nil } - it { expect(champ.mandatory_and_blank?).to be(true) } + it { expect(champ.mandatory_blank_and_visible?).to be(true) } end context 'when multiple_drop_down_list mandatory and blank' do let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, mandatory: mandatory) } let(:champ) { build(:champ_multiple_drop_down_list, type_de_champ: type_de_champ, value: value) } let(:value) { '[]' } - it { expect(champ.mandatory_and_blank?).to be(true) } + it { expect(champ.mandatory_blank_and_visible?).to be(true) } end context 'when repetition blank' do @@ -39,18 +39,18 @@ shared_examples 'champ_spec' do context 'when not blank' do let(:value) { 'yop' } - it { expect(champ.mandatory_and_blank?).to be(false) } + it { expect(champ.mandatory_blank_and_visible?).to be(false) } end context 'when not mandatory' do let(:mandatory) { false } - it { expect(champ.mandatory_and_blank?).to be(false) } + it { expect(champ.mandatory_blank_and_visible?).to be(false) } end context 'when not mandatory or blank' do let(:value) { 'u' } let(:mandatory) { false } - it { expect(champ.mandatory_and_blank?).to be(false) } + it { expect(champ.mandatory_blank_and_visible?).to be(false) } end end diff --git a/spec/models/champs/linked_drop_down_list_champ_spec.rb b/spec/models/champs/linked_drop_down_list_champ_spec.rb index bf53cc1b4..d58889cbf 100644 --- a/spec/models/champs/linked_drop_down_list_champ_spec.rb +++ b/spec/models/champs/linked_drop_down_list_champ_spec.rb @@ -78,7 +78,7 @@ describe Champs::LinkedDropDownListChamp do let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, drop_down_list_value: value) } it 'blank is fine' do - is_expected.not_to be_mandatory_and_blank + is_expected.not_to be_mandatory_blank_and_visible end end @@ -86,27 +86,27 @@ describe Champs::LinkedDropDownListChamp do let(:type_de_champ) { build(:type_de_champ_linked_drop_down_list, mandatory: true, drop_down_list_value: value) } context 'when there is no value' do - it { is_expected.to be_mandatory_and_blank } + it { is_expected.to be_mandatory_blank_and_visible } end context 'when there is a primary value' do before { subject.primary_value = 'Primary' } context 'when there is no secondary value' do - it { is_expected.to be_mandatory_and_blank } + it { is_expected.to be_mandatory_blank_and_visible } end context 'when there is a secondary value' do before { subject.secondary_value = 'Secondary' } - it { is_expected.not_to be_mandatory_and_blank } + it { is_expected.not_to be_mandatory_blank_and_visible } end context 'when there is nothing to select for the secondary value' do let(:value) { "--A--\nAbbott\nAbelard\n--B--\n--C--\nCynthia" } before { subject.primary_value = 'B' } - it { is_expected.not_to be_mandatory_and_blank } + it { is_expected.not_to be_mandatory_blank_and_visible } end end end From e82f0fd0c8a10df3a8e40c2e3c9840f2021ef0dc Mon Sep 17 00:00:00 2001 From: Paul Chavard Date: Mon, 27 Jun 2022 11:56:35 +0200 Subject: [PATCH 3/3] feat(dossier): exclude conditionally invisible fields from instructeur interface --- app/views/shared/dossiers/_champ_row.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/dossiers/_champ_row.html.haml b/app/views/shared/dossiers/_champ_row.html.haml index edb7f0788..d248177aa 100644 --- a/app/views/shared/dossiers/_champ_row.html.haml +++ b/app/views/shared/dossiers/_champ_row.html.haml @@ -1,4 +1,4 @@ -- champs.reject(&:exclude_from_view?).each do |c| +- champs.reject(&:exclude_from_view?).filter(&:visible?).each do |c| - if c.type_champ == TypeDeChamp.type_champs.fetch(:repetition) %tr %td.libelle.repetition{ colspan: 3 }