Skip to content

Commit

Permalink
tech(clean): simplify implementation of eligibilite rules, code, enha…
Browse files Browse the repository at this point in the history
…nce wording and test coverage
  • Loading branch information
mfo committed Jun 11, 2024
1 parent a011576 commit f819da8
Show file tree
Hide file tree
Showing 29 changed files with 161 additions and 219 deletions.
9 changes: 9 additions & 0 deletions app/assets/stylesheets/conditions_component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,14 @@ form.form > .conditionnel {
select.alert {
border-color: $dark-red;
}

&:first-child {
padding-left: 0;
}

&:last-child {
text-align: right;
padding-right: 0;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
%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
= form_for(@draft_revision, url: change_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id), html: { id: 'ineligibilite_form', class: 'width-100' }) do |f|
.fr-fieldset__element
.fr-toggle.fr-toggle--label-left
= f.check_box :ineligibilite_enabled, class: 'fr-toggle__input', data: @opt
= f.label :ineligibilite_enabled, "Bloquer le dépôt des dossiers répondant à des conditions d’inéligibilité", data: { 'fr-checked-label': "Activé", 'fr-unchecked-label': "Désactivé" }, class: 'fr-toggle__label'

.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :ineligibilite_message, input_type: :text_area, opts: {rows: 5})

.fr-mx-1w.fr-label.fr-py-0.fr-mb-1w.fr-mt-2w
Conditions d’inéligibilité
%span.fr-hint-text Vous pouvez utiliser une ou plusieurs condtions 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
- if rows.size > 0
%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
Expand All @@ -28,15 +37,13 @@
%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')
.padded-fixed-footer
.fixed-footer
.fr-container
.fr-grid-row.fr-col-offset-md-2.fr-col-md-8
.fr-col-12
%ul.fr-btns-group.fr-btns-group--inline-md
%li
= link_to "Annuler et revenir à l'écran de gestion", admin_procedure_path(id: @draft_revision.procedure), class: 'fr-btn fr-btn--secondary', data: { confirm: 'Si vous avez fait des modifications elles ne seront pas sauvegardées.'}
%li
= button_tag "Enregistrer", class: "fr-btn", form: 'ineligibilite_form'
10 changes: 3 additions & 7 deletions app/components/dossiers/edit_footer_component.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Dossiers::EditFooterComponent < ApplicationComponent
delegate :can_passer_en_construction?, :ineligibilite_rules_computable?, to: :@dossier
delegate :can_passer_en_construction?, to: :@dossier

def initialize(dossier:, annotation:)
@dossier = dossier
Expand Down Expand Up @@ -27,7 +27,7 @@ def disabled_submit_buttons_options
def submit_draft_button_options
{
class: 'fr-btn fr-btn--sm',
disabled: !owner? || ineligibilite_rules_invalid?,
disabled: !owner? || !can_passer_en_construction?,
method: :post,
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server }
}
Expand All @@ -36,17 +36,13 @@ def submit_draft_button_options
def submit_en_construction_button_options
{
class: 'fr-btn fr-btn--sm',
disabled: ineligibilite_rules_invalid?,
disabled: !can_passer_en_construction?,
method: :post,
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server },
form: { id: "form-submit-en-construction" }
}
end

def ineligibilite_rules_invalid?
ineligibilite_rules_computable? && !can_passer_en_construction?
end

def render?
!@dossier.for_procedure_preview?
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)

- if !annotation? && @dossier.can_transition_to_en_construction?
- if ineligibilite_rules_invalid?
- if !can_passer_en_construction?
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options

- if @dossier.forked_with_changes?
- if ineligibilite_rules_invalid?
- if !can_passer_en_construction?
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
= button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent
delegate :can_passer_en_construction?, :ineligibilite_rules_computable?, to: :@dossier
delegate :can_passer_en_construction?, to: :@dossier

def initialize(dossier:)
@dossier = dossier
@revision = dossier.revision
end

def render?
ineligibilite_rules_computable? && !can_passer_en_construction?
!can_passer_en_construction?
end

def error_message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def initialize(procedure:)
def ready?
@procedure.draft_revision
.conditionable_types_de_champ
.present?
.present? && @procedure.draft_revision.ineligibilite_enabled
end

def error?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
fr:
title: Inéligibilité des dossiers
state:
pending: Champs à configurer
pending: Désactivé
ready: À configurer
completed: Activé
subtitle: Gérez vos critères d’inéligibilité en fonction des champs du formulaire
subtitle: Gérez vos conditions d’inéligibilité en fonction des champs du formulaire
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
= link_to edit_admin_procedure_ineligibilite_rules_path(@procedure), class: 'fr-tile fr-enlarge-link' do
.fr-tile__body.flex.column.align-center.justify-between
- if !ready?
%p.fr-badge.fr-badge--warning= t('.state.pending')
%p.fr-badge.fr-badge= t('.state.pending')
- elsif error?
%p.fr-badge.fr-badge--error À modifier
- elsif !completed?
%p.fr-badge.fr-badge--info= t('.state.ready')
- else
%p.fr-badge.fr-badge--success= t('.state.completed')
%div
Expand Down
3 changes: 0 additions & 3 deletions app/controllers/users/dossiers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,13 +302,10 @@ def submit_en_construction
def update
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
@dossier = dossier_with_champs(pj_template: false)
@ineligibilite_rules_was_computable = @dossier.ineligibilite_rules_computable?
@can_passer_en_construction_was = @dossier.can_passer_en_construction?
update_dossier_and_compute_errors
@dossier.index_search_terms_later if @dossier.errors.empty?
@ineligibilite_rules_is_computable = @dossier.ineligibilite_rules_computable?
@can_passer_en_construction_is = @dossier.can_passer_en_construction?
@ineligibilite_rules_computable_changed = !@ineligibilite_rules_was_computable && @ineligibilite_rules_is_computable
respond_to do |format|
format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))
Expand Down
7 changes: 2 additions & 5 deletions app/models/dossier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -940,12 +940,9 @@ def check_mandatory_and_visible_champs
.filter(&:visible?)
.filter(&:mandatory_blank?)
.map do |champ|
errors.import(champ.errors.add(:value, :missing))
champ.errors.add(:value, :missing)
end
end

def ineligibilite_rules_computable?
revision.ineligibilite_rules_computable?(champs_for_revision(scope: :public))
.each { errors.import(_1) }
end

def demander_un_avis!(avis)
Expand Down
7 changes: 0 additions & 7 deletions app/models/logic/and.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,5 @@ def compute(champs = [])
@operands.map { |operand| operand.compute(champs) }.all?
end

def computable?(champs = [])
return true if sources.blank?

champs.filter { _1.stable_id.in?(sources) && _1.visible? }
.all? { _1.value.present? }
end

def to_s(type_de_champs) = "(#{@operands.map { |o| o.to_s(type_de_champs) }.join(' && ')})"
end
9 changes: 0 additions & 9 deletions app/models/logic/binary_operator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,6 @@ def compute(champs = [])
l&.send(operation, r) || false
end

def computable?(champs = [])
return true if sources.blank?

visible_champs_sources = champs.filter { _1.stable_id.in?(sources) && _1.visible? }

return false if visible_champs_sources.size != sources.size
visible_champs_sources.all? { _1.value.present? }
end

def to_s(type_de_champs) = "(#{@left.to_s(type_de_champs)} #{operation} #{@right.to_s(type_de_champs)})"

def ==(other)
Expand Down
10 changes: 0 additions & 10 deletions app/models/logic/or.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,5 @@ def compute(champs = [])
@operands.map { |operand| operand.compute(champs) }.any?
end


def computable?(champs = [])
return true if sources.blank?

visible_champs_sources = champs.filter { _1.stable_id.in?(sources) && _1.visible? }

return false if visible_champs_sources.blank?
visible_champs_sources.all? { _1.value.present? } || compute(visible_champs_sources)
end

def to_s(type_de_champs = []) = "(#{@operands.map { |o| o.to_s(type_de_champs) }.join(' || ')})"
end
13 changes: 0 additions & 13 deletions app/models/procedure_revision.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,6 @@ def conditionable_types_de_champ
types_de_champ_for(scope: :public).filter(&:conditionable?)
end

def ineligibilite_rules_computable?(champs)
ineligibilite_enabled && ineligibilite_rules&.computable?(champs)
ensure
champs.map(&:reset_visible) # otherwise @visible is cached, then dossier can be updated. champs are not updated
end

private

def compute_estimated_fill_duration
Expand Down Expand Up @@ -496,13 +490,6 @@ def ineligibilite_rules_are_valid?
end
end

def ineligibilite_rules_are_valid?
if ineligibilite_rules
ineligibilite_rules.errors(types_de_champ_for(scope: :public).to_a)
.each { errors.add(:ineligibilite_rules, :invalid) }
end
end

def replace_type_de_champ_by_clone(coordinate)
cloned_type_de_champ = coordinate.type_de_champ.deep_clone do |original, kopy|
ClonePiecesJustificativesService.clone_attachments(original, kopy)
Expand Down
6 changes: 3 additions & 3 deletions app/views/administrateurs/ineligibilite_rules/edit.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
= 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
Les dossiers répondant à vos conditions 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 [email protected]_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 :
Pour configurer l’inéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les conditions 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'
= link_to 'Ajouter un champ supportant les conditions 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)
7 changes: 3 additions & 4 deletions app/views/users/dossiers/update.turbo_stream.haml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
= render partial: 'shared/dossiers/update_champs', locals: { to_show: @to_show, to_hide: @to_hide, to_update: @to_update, dossier: @dossier }

- if !params.key?(:validate)
- if @ineligibilite_rules_is_computable
= turbo_stream.remove(dom_id(@dossier, :ineligibilite_rules_broken))

- if (@ineligibilite_rules_computable_changed && !@can_passer_en_construction_is) || (@can_passer_en_construction_was && !@can_passer_en_construction_is)
- if @can_passer_en_construction_was && !@can_passer_en_construction_is
= turbo_stream.append('contenu', render(Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: @dossier)))
- else @ineligibilite_rules_is_computable
= turbo_stream.remove(dom_id(@dossier, :ineligibilite_rules_broken))
2 changes: 1 addition & 1 deletion config/locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ fr:
otp_attempt: 'Code OTP (uniquement si vous avez déjà activé 2FA)'
procedure:
zone: La démarche est mise en œuvre par
ineligibilite_rules: "Les règles d’Inéligibilité"
ineligibilite_rules: "Les règles d’inéligibilité"
champs:
value: Valeur du champ
default_mail_attributes: &default_mail_attributes
Expand Down
2 changes: 1 addition & 1 deletion config/locales/models/procedure/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fr:
procedure:
hints:
description: Décrivez en quelques lignes le contexte, la finalité, etc.
description_target_audience: Décrivez en quelques lignes les destinataires finaux de la démarche, les critères d’éligibilité s’il y en a, les pré-requis, etc.
description_target_audience: Décrivez en quelques lignes les destinataires finaux de la démarche, les conditions d’éligibilité s’il y en a, les pré-requis, etc.
description_pj: Décrivez la liste des pièces jointes à fournir s’il y en a
lien_site_web: "Il s'agit de la page de votre site web où le lien sera diffusé. Ex: https://exemple.gouv.fr/page_informant_sur_ma_demarche"
cadre_juridique: "Exemple: 'https://www.legifrance.gouv.fr/'"
Expand Down
7 changes: 7 additions & 0 deletions config/locales/models/procedure_revision/fr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fr:
activerecord:
attributes:
procedure_revision:
ineligibilite_message: Message d’inéligibilité
hints:
ineligibilite_message: "Ce message sera affiché à l’usager si son dossier est bloqué et lui expliquera la raison de son inéligibilité."
8 changes: 4 additions & 4 deletions spec/components/dossiers/edit_footer_component_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
let(:dossier) { create(:dossier, :brouillon) }

context 'when dossier can be submitted' do
before { allow(component).to receive(:ineligibilite_rules_invalid?).and_return(false) }
before { allow(component).to receive(:can_passer_en_construction?).and_return(true) }
it 'renders submit button without disabled' do
expect(subject).to have_selector('button', text: 'Déposer le dossier')
end
end

context 'when dossier can not be submitted' do
before { allow(component).to receive(:ineligibilite_rules_invalid?).and_return(true) }
before { allow(component).to receive(:can_passer_en_construction?).and_return(false) }
it 'renders submit button with disabled' do
expect(subject).to have_selector('a', text: 'Pourquoi je ne peux pas déposer mon dossier ?')
expect(subject).to have_selector('button[disabled]', text: 'Déposer le dossier')
Expand All @@ -31,15 +31,15 @@
before { allow(dossier).to receive(:forked_with_changes?).and_return(true) }

context 'when dossier can be submitted' do
before { allow(component).to receive(:ineligibilite_rules_invalid?).and_return(false) }
before { allow(component).to receive(:can_passer_en_construction?).and_return(true) }

it 'renders submit button without disabled' do
expect(subject).to have_selector('button', text: 'Déposer les modifications')
end
end

context 'when dossier can not be submitted' do
before { allow(component).to receive(:ineligibilite_rules_invalid?).and_return(true) }
before { allow(component).to receive(:can_passer_en_construction?).and_return(false) }

it 'renders submit button with disabled' do
expect(subject).to have_selector('a', text: 'Pourquoi je ne peux pas déposer mon dossier ?')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
context 'types_de_champ_private' do
let(:is_annotation) { true }
it 'does not render public champs errors' do
expect(subject).to have_selector("a", "private")
expect(subject).to have_selector("a", text: "private")
expect(subject).to have_text("doit comporter au moins un choix sélectionnable")
expect(subject).not_to have_text("public")
end
Expand Down
Loading

0 comments on commit f819da8

Please sign in to comment.