Merge pull request #7907 from tchak/feat-cond-repetitions

feat(cond): enable conditional on champs in repetitions
This commit is contained in:
Paul Chavard 2022-12-08 14:20:25 +01:00 committed by GitHub
commit 1900f8ff4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 227 additions and 142 deletions

View file

@ -1,7 +1,8 @@
class TypesDeChampEditor::BlockComponent < ApplicationComponent class TypesDeChampEditor::BlockComponent < ApplicationComponent
def initialize(block:, coordinates:) def initialize(block:, coordinates:, upper_coordinates: [])
@block = block @block = block
@coordinates = coordinates @coordinates = coordinates
@upper_coordinates = upper_coordinates
end end
private private

View file

@ -1,3 +1,3 @@
%ul.types-de-champ-block{ id: block_id, data: sortable_options } %ul.types-de-champ-block{ id: block_id, data: sortable_options }
- @coordinates.each.with_index do |coordinate, i| - @coordinates.each.with_index do |coordinate, i|
= render TypesDeChampEditor::ChampComponent.new(coordinate: coordinate, upper_coordinates: @coordinates.take(i)) = render TypesDeChampEditor::ChampComponent.new(coordinate: coordinate, upper_coordinates: @upper_coordinates + @coordinates.take(i))

View file

@ -118,6 +118,6 @@ class TypesDeChampEditor::ChampComponent < ApplicationComponent
end end
def conditional_enabled? def conditional_enabled?
!type_de_champ.private? && !coordinate.child? !type_de_champ.private?
end end
end end

View file

@ -87,7 +87,7 @@
- if type_de_champ.block? - if type_de_champ.block?
.flex.justify-start.section.ml-1 .flex.justify-start.section.ml-1
.editor-block.flex-grow.cell .editor-block.flex-grow.cell
= render TypesDeChampEditor::BlockComponent.new(block: coordinate, coordinates: coordinate.revision_types_de_champ) = render TypesDeChampEditor::BlockComponent.new(block: coordinate, coordinates: coordinate.revision_types_de_champ, upper_coordinates: @upper_coordinates)
= 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?)
- if conditional_enabled? - if conditional_enabled?

View file

@ -55,7 +55,13 @@ module Administrateurs
def retrieve_coordinate_and_uppers def retrieve_coordinate_and_uppers
@tdc = draft_revision.find_and_ensure_exclusive_use(params[:stable_id]) @tdc = draft_revision.find_and_ensure_exclusive_use(params[:stable_id])
@coordinate = draft_revision.coordinate_for(@tdc) @coordinate = draft_revision.coordinate_for(@tdc)
@upper_tdcs = @coordinate.upper_siblings.map(&:type_de_champ)
upper_coordinates = @coordinate.upper_siblings
if @coordinate.child?
upper_coordinates += @coordinate.parent.upper_siblings
end
@upper_tdcs = upper_coordinates.map(&:type_de_champ)
end end
def draft_revision def draft_revision

View file

@ -199,7 +199,7 @@ module Users
respond_to do |format| respond_to do |format|
format.html { render :brouillon } format.html { render :brouillon }
format.turbo_stream do format.turbo_stream do
@to_shows, @to_hides = @dossier.champs_public @to_shows, @to_hides = @dossier.champs_public_all
.filter(&:conditional?) .filter(&:conditional?)
.partition(&:visible?) .partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) } .map { |champs| champs_to_one_selector(champs) }
@ -220,7 +220,7 @@ module Users
respond_to do |format| respond_to do |format|
format.html { render :modifier } format.html { render :modifier }
format.turbo_stream do format.turbo_stream do
@to_shows, @to_hides = @dossier.champs_public @to_shows, @to_hides = @dossier.champs_public_all
.filter(&:conditional?) .filter(&:conditional?)
.partition(&:visible?) .partition(&:visible?)
.map { |champs| champs_to_one_selector(champs) } .map { |champs| champs_to_one_selector(champs) }

View file

@ -237,7 +237,7 @@ class Champ < ApplicationRecord
private private
def champs_for_condition def champs_for_condition
private? ? dossier.champs_private : dossier.champs_public dossier.champs.filter { _1.row.nil? || _1.row == row }
end end
def html_id def html_id

View file

@ -78,8 +78,10 @@ class Dossier < ApplicationRecord
has_one_attached :justificatif_motivation has_one_attached :justificatif_motivation
has_many :champs
has_many :champs_public, -> { root.public_ordered }, class_name: 'Champ', inverse_of: false, dependent: :destroy has_many :champs_public, -> { root.public_ordered }, class_name: 'Champ', inverse_of: false, dependent: :destroy
has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: false, dependent: :destroy has_many :champs_private, -> { root.private_ordered }, class_name: 'Champ', inverse_of: false, dependent: :destroy
has_many :champs_public_all, -> { public_only }, class_name: 'Champ', inverse_of: false
has_many :commentaires, inverse_of: :dossier, dependent: :destroy has_many :commentaires, inverse_of: :dossier, dependent: :destroy
has_many :invites, dependent: :destroy has_many :invites, dependent: :destroy
has_many :follows, -> { active }, inverse_of: :dossier has_many :follows, -> { active }, inverse_of: :dossier

View file

@ -78,6 +78,7 @@ class DossierPreloader
def load_dossier(dossier, champs, children_by_parent = {}) def load_dossier(dossier, champs, children_by_parent = {})
champs_public, champs_private = champs.partition(&:public?) champs_public, champs_private = champs.partition(&:public?)
dossier.association(:champs).target = []
load_champs(dossier, :champs_public, champs_public, dossier, children_by_parent) load_champs(dossier, :champs_public, champs_public, dossier, children_by_parent)
load_champs(dossier, :champs_private, champs_private, dossier, children_by_parent) load_champs(dossier, :champs_private, champs_private, dossier, children_by_parent)
@ -97,6 +98,7 @@ class DossierPreloader
champ.association(:parent).target = parent champ.association(:parent).target = parent
end end
end end
dossier.association(:champs).target += champs
parent.association(name).target = champs.sort_by do |champ| parent.association(name).target = champs.sort_by do |champ|
[champ.row, positions[dossier.revision_id][champ.type_de_champ_id]] [champ.row, positions[dossier.revision_id][champ.type_de_champ_id]]

View file

@ -1,118 +1,159 @@
describe Administrateurs::ConditionsController, type: :controller do describe Administrateurs::ConditionsController, type: :controller do
include Logic include Logic
let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :integer_number }] * 3) }
let(:first_coordinate) { procedure.draft_revision.revision_types_de_champ.first }
let(:second_coordinate) { procedure.draft_revision.revision_types_de_champ.first }
let(:third_coordinate) { procedure.draft_revision.revision_types_de_champ.first }
let(:first_tdc) { procedure.draft_revision.types_de_champ.first }
let(:second_tdc) { procedure.draft_revision.types_de_champ.second }
let(:third_tdc) { procedure.draft_revision.types_de_champ.third }
before { sign_in(procedure.administrateurs.first.user) } before { sign_in(procedure.administrateurs.first.user) }
let(:default_params) do context 'without bloc repetition' do
{ let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :integer_number }] * 3) }
procedure_id: procedure.id, let(:first_tdc) { procedure.draft_revision.types_de_champ.first }
stable_id: third_tdc.stable_id let(:second_tdc) { procedure.draft_revision.types_de_champ.second }
} let(:third_tdc) { procedure.draft_revision.types_de_champ.third }
end
describe '#update' do before { sign_in(procedure.administrateurs.first.user) }
before { post :update, params: params, format: :turbo_stream }
let(:params) { default_params.merge(type_de_champ: { condition_form: condition_form }) } let(:default_params) do
let(:condition_form) do
{ {
rows: [ procedure_id: procedure.id,
{ stable_id: third_tdc.stable_id
targeted_champ: champ_value(first_tdc.stable_id).to_json,
operator_name: Logic::Eq.name,
value: '2'
}
]
} }
end end
it do describe '#update' do
expect(third_tdc.reload.condition).to eq(ds_eq(champ_value(first_tdc.stable_id), constant(2))) before { post :update, params: params, format: :turbo_stream }
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc]) let(:params) { default_params.merge(type_de_champ: { condition_form: condition_form }) }
let(:condition_form) do
{
rows: [
{
targeted_champ: champ_value(first_tdc.stable_id).to_json,
operator_name: Logic::Eq.name,
value: '2'
}
]
}
end
it do
expect(third_tdc.reload.condition).to eq(ds_eq(champ_value(first_tdc.stable_id), constant(2)))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
describe '#add_row' do
before { post :add_row, params: default_params, format: :turbo_stream }
it do
expect(third_tdc.reload.condition).to eq(empty_operator(empty, empty))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
describe '#delete_row' do
before { delete :delete_row, params: params.merge(row_index: 0), format: :turbo_stream }
let(:params) { default_params.merge(type_de_champ: { condition_form: condition_form }) }
let(:condition_form) do
{
rows: [
{
targeted_champ: champ_value(1).to_json,
operator_name: Logic::Eq.name,
value: '2'
}
]
}
end
it do
expect(third_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
describe '#destroy' do
before do
second_tdc.update(condition: empty_operator(empty, empty))
delete :destroy, params: default_params, format: :turbo_stream
end
it do
expect(third_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
describe '#change_targeted_champ' do
before do
second_tdc.update(condition: empty_operator(empty, empty))
patch :change_targeted_champ, params: params, format: :turbo_stream
end
let(:params) { default_params.merge(type_de_champ: { condition_form: condition_form }) }
let(:condition_form) do
{
rows: [
{
targeted_champ: champ_value(second_tdc.stable_id).to_json,
operator_name: Logic::EmptyOperator.name,
value: empty.to_json
}
]
}
end
it do
expect(third_tdc.reload.condition).to eq(ds_eq(champ_value(second_tdc.stable_id), constant(0)))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end end
end end
describe '#add_row' do context 'with a repetiton bloc' do
before { post :add_row, params: default_params, format: :turbo_stream } let(:procedure) do
create(:procedure, types_de_champ_public: [
it do { type: :integer_number, libelle: 'top_1' },
expect(third_tdc.reload.condition).to eq(empty_operator(empty, empty)) {
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc)) type: :repetition,
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc]) libelle: 'repetition',
children: [
{ type: :integer_number, libelle: 'child_1' },
{ type: :integer_number, libelle: 'child_2' }
]
}
])
end end
end let(:tdcs) { procedure.draft_revision.types_de_champ }
let(:top) { tdcs.find_by(libelle: 'top_1') }
let(:repetition) { tdcs.find_by(libelle: 'repetition') }
let(:child_1) { tdcs.find_by(libelle: 'child_1') }
let(:child_2) { tdcs.find_by(libelle: 'child_2') }
describe '#delete_row' do let(:default_params) do
before { delete :delete_row, params: params.merge(row_index: 0), format: :turbo_stream }
let(:params) { default_params.merge(type_de_champ: { condition_form: condition_form }) }
let(:condition_form) do
{ {
rows: [ procedure_id: procedure.id,
{ stable_id: child_2.stable_id
targeted_champ: champ_value(1).to_json,
operator_name: Logic::Eq.name,
value: '2'
}
]
} }
end end
it do describe '#add_row' do
expect(third_tdc.reload.condition).to eq(nil) before do
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc)) post :add_row, params: default_params, format: :turbo_stream
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc]) end
end
end
describe '#destroy' do it do
before do expect(child_2.reload.condition).to eq(empty_operator(empty, empty))
second_tdc.update(condition: empty_operator(empty, empty)) expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(child_2))
delete :destroy, params: default_params, format: :turbo_stream expect(assigns(:upper_tdcs)).to eq([child_1, top])
end end
it do
expect(third_tdc.reload.condition).to eq(nil)
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end
end
describe '#change_targeted_champ' do
before do
second_tdc.update(condition: empty_operator(empty, empty))
patch :change_targeted_champ, params: params, format: :turbo_stream
end
let(:params) { default_params.merge(type_de_champ: { condition_form: condition_form }) }
let(:condition_form) do
{
rows: [
{
targeted_champ: champ_value(second_tdc.stable_id).to_json,
operator_name: Logic::EmptyOperator.name,
value: empty.to_json
}
]
}
end
it do
expect(third_tdc.reload.condition).to eq(ds_eq(champ_value(second_tdc.stable_id), constant(0)))
expect(assigns(:coordinate)).to eq(procedure.draft_revision.coordinate_for(third_tdc))
expect(assigns(:upper_tdcs)).to eq([first_tdc, second_tdc])
end end
end end
end end

View file

@ -289,52 +289,87 @@ describe 'The user' do
include Logic include Logic
context 'with a repetition' do context 'with a repetition' do
let(:stable_id) { 999 }
let(:condition) { greater_than_eq(champ_value(stable_id), constant(18)) }
let(:procedure) do let(:procedure) do
procedure = create(:procedure, :published, :for_individual, create(:procedure, :published, :for_individual,
types_de_champ_public: [ types_de_champ_public: [
{ type: :integer_number, libelle: 'age' }, { type: :integer_number, libelle: 'age', stable_id: },
{ {
type: :repetition, libelle: 'repetition', children: [ type: :repetition, libelle: 'repetition', condition:, children: [
{ type: :text, libelle: 'nom', mandatory: true } { type: :text, libelle: 'nom', mandatory: true }
] ]
} }
]) ])
age = procedure.published_revision.types_de_champ.where(libelle: 'age').first
repetition = procedure.published_revision.types_de_champ.repetition.first
repetition.update(condition: greater_than_eq(champ_value(age.stable_id), constant(18)))
procedure
end end
scenario 'submit a dossier with an hidden mandatory champ within a repetition', js: true do scenario 'submit a dossier with an hidden mandatory champ within a repetition', js: true do
log_in(user, procedure) log_in(user, procedure)
fill_individual fill_individual
fill_in('age', with: 10) fill_in('age', with: 10)
click_on 'Déposer le dossier' click_on 'Déposer le dossier'
expect(page).to have_current_path(merci_dossier_path(user_dossier)) expect(page).to have_current_path(merci_dossier_path(user_dossier))
end end
end end
context 'with a required conditionnal champ' do context 'with a condition inside repetition' do
let(:a_stable_id) { 999 }
let(:b_stable_id) { 9999 }
let(:a_condition) { ds_eq(champ_value(a_stable_id), constant(true)) }
let(:b_condition) { ds_eq(champ_value(b_stable_id), constant(true)) }
let(:condition) { ds_or([a_condition, b_condition]) }
let(:procedure) do let(:procedure) do
procedure = create(:procedure, :published, :for_individual, create(:procedure, :published, :for_individual,
types_de_champ_public: [ types_de_champ_public: [
{ type: :integer_number, libelle: 'age' }, { type: :checkbox, libelle: 'champ_a', stable_id: a_stable_id },
{ type: :text, libelle: 'nom', mandatory: true } {
]) type: :repetition, libelle: 'repetition', children: [
{ type: :checkbox, libelle: 'champ_b', stable_id: b_stable_id },
{ type: :text, libelle: 'champ_c', condition: }
]
}
])
end
age, nom = procedure.draft_revision.types_de_champ.all scenario 'fill a dossier', js: true do
nom.update(condition: greater_than_eq(champ_value(age.stable_id), constant(18))) log_in(user, procedure)
procedure fill_individual
expect(page).to have_no_css('label', text: 'champ_c', visible: true)
check('champ_a')
wait_for_autosave
expect(page).to have_css('label', text: 'champ_c', visible: true)
uncheck('champ_a')
wait_for_autosave
expect(page).to have_no_css('label', text: 'champ_c', visible: true)
check('champ_b')
wait_for_autosave
expect(page).to have_css('label', text: 'champ_c', visible: true)
end
end
context 'with a required conditionnal champ' do
let(:stable_id) { 999 }
let(:condition) { greater_than_eq(champ_value(stable_id), constant(18)) }
let(:procedure) do
create(:procedure, :published, :for_individual,
types_de_champ_public: [
{ type: :integer_number, libelle: 'age', stable_id: },
{ type: :text, libelle: 'nom', mandatory: true, condition: }
])
end end
scenario 'submit a dossier with an hidden mandatory champ ', js: true do scenario 'submit a dossier with an hidden mandatory champ ', js: true do
log_in(user, procedure) log_in(user, procedure)
fill_individual fill_individual
click_on 'Déposer le dossier' click_on 'Déposer le dossier'
expect(page).to have_current_path(merci_dossier_path(user_dossier)) expect(page).to have_current_path(merci_dossier_path(user_dossier))
end end
@ -353,23 +388,21 @@ describe 'The user' do
end end
context 'with a visibilite in cascade' do context 'with a visibilite in cascade' do
let(:age_stable_id) { 999 }
let(:permis_stable_id) { 9999 }
let(:tonnage_stable_id) { 99999 }
let(:permis_condition) { greater_than_eq(champ_value(age_stable_id), constant(18)) }
let(:tonnage_condition) { ds_eq(champ_value(permis_stable_id), constant(true)) }
let(:parking_condition) { less_than_eq(champ_value(tonnage_stable_id), constant(20)) }
let(:procedure) do let(:procedure) do
procedure = create(:procedure, :for_individual).tap do |p| create(:procedure, :published, :for_individual,
p.draft_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'age') types_de_champ_public: [
p.draft_revision.add_type_de_champ(type_champ: :yes_no, libelle: 'permis de conduire') { type: :integer_number, libelle: 'age', stable_id: age_stable_id },
p.draft_revision.add_type_de_champ(type_champ: :integer_number, libelle: 'tonnage') { type: :yes_no, libelle: 'permis de conduire', stable_id: permis_stable_id, condition: permis_condition },
p.draft_revision.add_type_de_champ(type_champ: :text, libelle: 'parking') { type: :integer_number, libelle: 'tonnage', stable_id: tonnage_stable_id, condition: tonnage_condition },
end { type: :text, libelle: 'parking', condition: parking_condition }
])
age, permis, tonnage, parking = procedure.draft_revision.types_de_champ.all
permis.update(condition: greater_than_eq(champ_value(age.stable_id), constant(18)))
tonnage.update(condition: ds_eq(champ_value(permis.stable_id), constant(true)))
parking.update(condition: less_than_eq(champ_value(tonnage.stable_id), constant(20)))
procedure.publish!
procedure
end end
scenario 'fill a dossier', js: true do scenario 'fill a dossier', js: true do