Skip to content

Commit 730f81c

Browse files
tchakclaude
andcommitted
ETQ usager, une condition sur une section masque aussi les champs de la section
Add a section_conditions_hide_champs flag on procedures (false by default, set to true for newly created procedures). When enabled, a champ is hidden whenever any of its ancestors (header section or repetition) is hidden, using the champ tree API. Legacy procedures keep the previous behaviour where a section condition only hides the section header itself. - visible? now checks the nearest ancestor and recurses up the chain - turbo updates toggle non-conditional champs nested under conditional sections (conditional_visibility?) - PDF export skips champs hidden through the cascade - champs loaded through champs_on_stream now share their dossier instance (same technique as champ_for_update), so visibility memoization stays within one instance graph - a re-entrancy guard in visible? protects against section conditions targeting a champ inside the section itself (invalid draft config, reachable until publication blocks it) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6f7714b commit 730f81c

15 files changed

Lines changed: 285 additions & 17 deletions

app/controllers/administrateurs/procedures_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def zones
103103
end
104104

105105
def create
106-
new_procedure_params = { max_duree_conservation_dossiers_dans_ds: Expired::DEFAULT_DOSSIER_RENTENTION_IN_MONTH, no_gender: true }
106+
new_procedure_params = { max_duree_conservation_dossiers_dans_ds: Expired::DEFAULT_DOSSIER_RENTENTION_IN_MONTH, no_gender: true, section_conditions_hide_champs: true }
107107
.merge(procedure_params)
108108
.merge(administrateurs: [current_administrateur])
109109

app/controllers/concerns/turbo_champs_concern.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def champ_to_turbo_update(champ, champs)
2222
end
2323

2424
def champs_to_toggle(champs, to_update)
25-
champs.filter { it.conditional? || it.in_repetition? }
25+
champs.filter(&:conditional_visibility?)
2626
.partition(&:visible?)
2727
.map { champs_to_one_selector(it - to_update) }
2828
end

app/models/champ.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ class Champ < ApplicationRecord
1212

1313
attr_readonly :stable_id
1414

15+
# inverse_of stays disabled: the projection system builds transient champs
16+
# with `dossier: self`, and an inverse would register them (and autosave
17+
# them) into the loaded champ_data association. Reads share the dossier
18+
# instance via DossierChampsConcern#champs_on_stream instead.
1519
belongs_to :dossier, inverse_of: false, touch: true, optional: false
1620
has_many_attached :piece_justificative_file
1721

app/models/concerns/champ_conditional_concern.rb

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,37 @@ def dependent_conditions?
1111
dossier.revision.dependent_conditions(type_de_champ).any?
1212
end
1313

14+
def section_conditions_hide_champs?
15+
dossier.procedure.section_conditions_hide_champs?
16+
end
17+
18+
# visibility can vary with form data (own condition, repetition membership,
19+
# or a conditional ancestor section)
20+
def conditional_visibility?
21+
conditional? || in_repetition? || (section_conditions_hide_champs? && ancestors.any?(&:conditional?))
22+
end
23+
1424
def visible?
1525
# Huge gain perf for cascade conditions
1626
return @visible if instance_variable_defined? :@visible
1727

18-
return false if parent_hidden?
28+
# a section condition targeting a champ inside the section itself creates
29+
# a cycle (invalid draft config, reachable until publication); treat the
30+
# in-progress champ as hidden, so the broken condition computes to nil and
31+
# the section stays hidden
32+
return false if @visible_computing
1933

20-
@visible = if conditional?
21-
type_de_champ.condition.compute(champs_for_condition)
22-
else
23-
true
34+
@visible_computing = true
35+
begin
36+
return false if parent_hidden?
37+
38+
@visible = if conditional?
39+
type_de_champ.condition.compute(champs_for_condition)
40+
else
41+
true
42+
end
43+
ensure
44+
@visible_computing = false
2445
end
2546
end
2647

@@ -51,11 +72,18 @@ def filled_champs_by_row_id
5172
end
5273

5374
def parent_hidden?
54-
# if there is no row_id, it always has been a root champ
55-
return false if !in_repetition?
75+
if section_conditions_hide_champs?
76+
# hidden as soon as the nearest ancestor (section or repetition) is
77+
# hidden; the parent's own visible? recurses up the ancestor chain
78+
parent.present? && !parent.visible?
79+
else
80+
# legacy: only the repetition ancestor can hide a champ
81+
# if there is no row_id, it always has been a root champ
82+
return false if !in_repetition?
5683

57-
# otherwise maybe the champ has been moved outside a repetition
58-
parent = repetition
59-
parent.present? && !parent.visible?
84+
# otherwise maybe the champ has been moved outside a repetition
85+
parent = repetition
86+
parent.present? && !parent.visible?
87+
end
6088
end
6189
end

app/models/concerns/dossier_champs_concern.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,15 @@ def discarded_champs_by_public_id
356356
end
357357

358358
def champs_on_stream
359-
@champs_on_stream ||= if user_buffer_stream?
359+
@champs_on_stream ||= champs_for_current_stream
360+
# champ.dossier is loaded without inverse_of; share this dossier
361+
# instance so tree navigation (parent, ancestors) and visibility
362+
# memoization stay within one instance graph
363+
.each { it.association(:dossier).target = self }
364+
end
365+
366+
def champs_for_current_stream
367+
if user_buffer_stream?
360368
(champs_on_user_buffer_stream + champs_on_main_stream).uniq(&:public_id)
361369
elsif instructeur_buffer_stream?
362370
(champs_on_instructeur_buffer_stream + champs_on_main_stream).uniq(&:public_id)

app/models/concerns/procedure_clone_concern.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ module ProcedureCloneConcern
8080
'routing_alert',
8181
'api_particulier_token',
8282
'no_gender',
83+
'section_conditions_hide_champs',
8384
'pro_connect_restriction',
8485
'pro_connect_for_moral_procedure',
8586
'robots_indexable',

app/views/dossiers/show.pdf.prawn

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ end
227227

228228
def add_single_champ(pdf, champ)
229229
tdc = champ.type_de_champ
230-
return if champ.conditional? && !champ.visible?
230+
return if !champ.visible? && (champ.conditional? || champ.section_conditions_hide_champs?)
231231

232232
case champ.type
233233
when 'Champs::PieceJustificativeChamp'
@@ -307,7 +307,7 @@ def add_champs(pdf, champs)
307307

308308
champs.each do |champ|
309309
if champ.type == 'Champs::HeaderSectionChamp'
310-
next if champ.conditional? && !champ.visible?
310+
next if !champ.visible? && (champ.conditional? || champ.section_conditions_hide_champs?)
311311
current_indent = champ.level * section_indent_step
312312
add_single_champ(pdf, champ)
313313
elsif champ.repetition?
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class AddSectionConditionsHideChampsToProcedures < ActiveRecord::Migration[8.0]
4+
def change
5+
add_column :procedures, :section_conditions_hide_champs, :boolean, default: false, null: false
6+
end
7+
end

db/schema.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#
1212
# It's strongly recommended that you check this file into your version control system.
1313

14-
ActiveRecord::Schema[8.0].define(version: 2026_07_13_000000) do
14+
ActiveRecord::Schema[8.0].define(version: 2026_07_17_000000) do
1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "pg_buffercache"
1717
enable_extension "pg_stat_statements"
@@ -1156,6 +1156,7 @@
11561156
t.boolean "robots_indexable", default: true, null: false
11571157
t.boolean "routing_alert", default: false, null: false
11581158
t.boolean "routing_enabled"
1159+
t.boolean "section_conditions_hide_champs", default: false, null: false
11591160
t.bigint "service_id"
11601161
t.jsonb "sva_svr", default: {}, null: false
11611162
t.text "tags", default: [], array: true

spec/controllers/users/dossiers_controller_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,39 @@
10041004
end
10051005
end
10061006

1007+
context 'when a section is conditioned by the updated champ' do
1008+
include Logic
1009+
1010+
let(:types_de_champ_public) do
1011+
[
1012+
{ type: :yes_no, stable_id: 1 },
1013+
{ type: :header_section, level: 1, stable_id: 10, condition: ds_eq(champ_value(1), constant(true)) },
1014+
{ type: :text, stable_id: 11 },
1015+
]
1016+
end
1017+
let(:value) { 'false' }
1018+
1019+
def selector_for(stable_id)
1020+
"##{assigns(:dossier).flat_champs_public.find { it.stable_id == stable_id }.input_group_id}"
1021+
end
1022+
1023+
it 'hides the section and the champs under it' do
1024+
subject
1025+
expect(assigns(:to_hide)).to include(selector_for(10))
1026+
expect(assigns(:to_hide)).to include(selector_for(11))
1027+
end
1028+
1029+
context 'when the procedure uses the legacy behaviour' do
1030+
before { procedure.update!(section_conditions_hide_champs: false) }
1031+
1032+
it 'hides only the section' do
1033+
subject
1034+
expect(assigns(:to_hide)).to include(selector_for(10))
1035+
expect(assigns(:to_hide)).not_to include(selector_for(11))
1036+
end
1037+
end
1038+
end
1039+
10071040
context 'when the champ is a drop_down_list with referentiel' do
10081041
let(:procedure) { create(:procedure, :published, types_de_champ_public: [{ type: :drop_down_list }]) }
10091042

0 commit comments

Comments
 (0)