tech(clean): simplify implementation of eligibilite rules, code, enhance wording and test coverage

This commit is contained in:
mfo 2024-06-05 19:16:41 +02:00
parent a011576757
commit f819da8921
No known key found for this signature in database
GPG key ID: 7CE3E1F5B794A8EC
29 changed files with 161 additions and 219 deletions

View file

@ -57,5 +57,14 @@ form.form > .conditionnel {
select.alert { select.alert {
border-color: $dark-red; border-color: $dark-red;
} }
&:first-child {
padding-left: 0;
}
&:last-child {
text-align: right;
padding-right: 0;
}
} }
} }

View file

@ -1,14 +1,23 @@
%div{ id: dom_id(@draft_revision, :ineligibilite_rules) } %div{ id: dom_id(@draft_revision, :ineligibilite_rules) }
= render Procedure::PendingRepublishComponent.new(procedure: @draft_revision.procedure, render_if: pending_changes?) = render Procedure::PendingRepublishComponent.new(procedure: @draft_revision.procedure, render_if: pending_changes?)
= render Conditions::ConditionsErrorsComponent.new(conditions: condition_per_row, source_tdcs: @source_tdcs) = render Conditions::ConditionsErrorsComponent.new(conditions: condition_per_row, source_tdcs: @source_tdcs)
%fieldset.fr-fieldset .fr-fieldset
%legend.fr-mx-1w.fr-label.fr-py-0.fr-mb-1w.fr-mt-2w = form_for(@draft_revision, url: change_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id), html: { id: 'ineligibilite_form', class: 'width-100' }) do |f|
Règles dinéligibilité .fr-fieldset__element
%span.fr-hint-text Vous pouvez utiliser 1 ou plusieurs critère pour bloquer le dépot .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 diné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 dinéligibilité
%span.fr-hint-text Vous pouvez utiliser une ou plusieurs condtions pour bloquer le dépot.
.fr-fieldset__element .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 = 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 .conditionnel.width-100
%table.condition-table %table.condition-table
- if rows.size > 0
%thead %thead
%tr %tr
%th.fr-pt-0.far-left %th.fr-pt-0.far-left
@ -28,15 +37,13 @@
%tr %tr
%td.text-right{ colspan: 5 }= add_condition_tag %td.text-right{ colspan: 5 }= add_condition_tag
.padded-fixed-footer
.fixed-footer
= form_for(@draft_revision, url: change_admin_procedure_ineligibilite_rules_path(@draft_revision.procedure_id)) do |f| .fr-container
.fr-fieldset__element= render Dsfr::InputComponent.new(form: f, attribute: :ineligibilite_message, input_type: :text_area, opts: {rows: 5}) .fr-grid-row.fr-col-offset-md-2.fr-col-md-8
.fr-fieldset__element .fr-col-12
.fr-toggle %ul.fr-btns-group.fr-btns-group--inline-md
= f.check_box :ineligibilite_enabled, class: 'fr-toggle__input', data: @opt %li
= f.label :ineligibilite_enabled, "Inéligibilité des dossiers", data: { 'fr-checked-label': "Actif", 'fr-unchecked-label': "Inactif" }, class: 'fr-toggle__label' = 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.'}
%p.fr-hint-text Passer lintérrupteur sur activé pour que les critères dinéligibilité configurés s'appliquent %li
= button_tag "Enregistrer", class: "fr-btn", form: 'ineligibilite_form'
= render Procedure::FixedFooterComponent.new(procedure: @draft_revision.procedure, form: f, extra_class_names: 'fr-col-offset-md-2 fr-col-md-8')

View file

@ -1,5 +1,5 @@
class Dossiers::EditFooterComponent < ApplicationComponent 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:) def initialize(dossier:, annotation:)
@dossier = dossier @dossier = dossier
@ -27,7 +27,7 @@ class Dossiers::EditFooterComponent < ApplicationComponent
def submit_draft_button_options def submit_draft_button_options
{ {
class: 'fr-btn fr-btn--sm', class: 'fr-btn fr-btn--sm',
disabled: !owner? || ineligibilite_rules_invalid?, disabled: !owner? || !can_passer_en_construction?,
method: :post, method: :post,
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server } data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server }
} }
@ -36,17 +36,13 @@ class Dossiers::EditFooterComponent < ApplicationComponent
def submit_en_construction_button_options def submit_en_construction_button_options
{ {
class: 'fr-btn fr-btn--sm', class: 'fr-btn fr-btn--sm',
disabled: ineligibilite_rules_invalid?, disabled: !can_passer_en_construction?,
method: :post, method: :post,
data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server }, data: { 'disable-with': t('.submitting'), controller: 'autosave-submit', turbo_force: :server },
form: { id: "form-submit-en-construction" } form: { id: "form-submit-en-construction" }
} }
end end
def ineligibilite_rules_invalid?
ineligibilite_rules_computable? && !can_passer_en_construction?
end
def render? def render?
!@dossier.for_procedure_preview? !@dossier.for_procedure_preview?
end end

View file

@ -3,12 +3,12 @@
= render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?) = render Dossiers::AutosaveFooterComponent.new(dossier: @dossier, annotation: annotation?)
- if !annotation? && @dossier.can_transition_to_en_construction? - 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 = link_to t('.submit_disabled'), "#", disabled_submit_buttons_options
= button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options = button_to t('.submit'), brouillon_dossier_url(@dossier), submit_draft_button_options
- if @dossier.forked_with_changes? - if @dossier.forked_with_changes?
- if ineligibilite_rules_invalid? - if !can_passer_en_construction?
= link_to t('.submit_disabled'), "#", disabled_submit_buttons_options = 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 = button_to t('.submit_changes'), modifier_dossier_url(@dossier.editing_fork_origin), submit_en_construction_button_options

View file

@ -1,5 +1,5 @@
class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent
delegate :can_passer_en_construction?, :ineligibilite_rules_computable?, to: :@dossier delegate :can_passer_en_construction?, to: :@dossier
def initialize(dossier:) def initialize(dossier:)
@dossier = dossier @dossier = dossier
@ -7,7 +7,7 @@ class Dossiers::InvalidIneligibiliteRulesComponent < ApplicationComponent
end end
def render? def render?
ineligibilite_rules_computable? && !can_passer_en_construction? !can_passer_en_construction?
end end
def error_message def error_message

View file

@ -6,7 +6,7 @@ class Procedure::Card::IneligibiliteDossierComponent < ApplicationComponent
def ready? def ready?
@procedure.draft_revision @procedure.draft_revision
.conditionable_types_de_champ .conditionable_types_de_champ
.present? .present? && @procedure.draft_revision.ineligibilite_enabled
end end
def error? def error?

View file

@ -2,7 +2,7 @@
fr: fr:
title: Inéligibilité des dossiers title: Inéligibilité des dossiers
state: state:
pending: Champs à configurer pending: Désactivé
ready: À configurer ready: À configurer
completed: Activé completed: Activé
subtitle: Gérez vos critères dinéligibilité en fonction des champs du formulaire subtitle: Gérez vos conditions dinéligibilité en fonction des champs du formulaire

View file

@ -2,11 +2,9 @@
= link_to edit_admin_procedure_ineligibilite_rules_path(@procedure), class: 'fr-tile fr-enlarge-link' do = 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 .fr-tile__body.flex.column.align-center.justify-between
- if !ready? - if !ready?
%p.fr-badge.fr-badge--warning= t('.state.pending') %p.fr-badge.fr-badge= t('.state.pending')
- elsif error? - elsif error?
%p.fr-badge.fr-badge--error À modifier %p.fr-badge.fr-badge--error À modifier
- elsif !completed?
%p.fr-badge.fr-badge--info= t('.state.ready')
- else - else
%p.fr-badge.fr-badge--success= t('.state.completed') %p.fr-badge.fr-badge--success= t('.state.completed')
%div %div

View file

@ -302,13 +302,10 @@ module Users
def update def update
@dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier @dossier = dossier.en_construction? ? dossier.find_editing_fork(dossier.user) : dossier
@dossier = dossier_with_champs(pj_template: false) @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? @can_passer_en_construction_was = @dossier.can_passer_en_construction?
update_dossier_and_compute_errors update_dossier_and_compute_errors
@dossier.index_search_terms_later if @dossier.errors.empty? @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? @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| respond_to do |format|
format.turbo_stream do format.turbo_stream do
@to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?)) @to_show, @to_hide, @to_update = champs_to_turbo_update(champs_public_attributes_params, dossier.champs.filter(&:public?))

View file

@ -940,12 +940,9 @@ class Dossier < ApplicationRecord
.filter(&:visible?) .filter(&:visible?)
.filter(&:mandatory_blank?) .filter(&:mandatory_blank?)
.map do |champ| .map do |champ|
errors.import(champ.errors.add(:value, :missing)) champ.errors.add(:value, :missing)
end end
end .each { errors.import(_1) }
def ineligibilite_rules_computable?
revision.ineligibilite_rules_computable?(champs_for_revision(scope: :public))
end end
def demander_un_avis!(avis) def demander_un_avis!(avis)

View file

@ -7,12 +7,5 @@ class Logic::And < Logic::NAryOperator
@operands.map { |operand| operand.compute(champs) }.all? @operands.map { |operand| operand.compute(champs) }.all?
end 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(' && ')})" def to_s(type_de_champs) = "(#{@operands.map { |o| o.to_s(type_de_champs) }.join(' && ')})"
end end

View file

@ -42,15 +42,6 @@ class Logic::BinaryOperator < Logic::Term
l&.send(operation, r) || false l&.send(operation, r) || false
end 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 to_s(type_de_champs) = "(#{@left.to_s(type_de_champs)} #{operation} #{@right.to_s(type_de_champs)})"
def ==(other) def ==(other)

View file

@ -7,15 +7,5 @@ class Logic::Or < Logic::NAryOperator
@operands.map { |operand| operand.compute(champs) }.any? @operands.map { |operand| operand.compute(champs) }.any?
end 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(' || ')})" def to_s(type_de_champs = []) = "(#{@operands.map { |o| o.to_s(type_de_champs) }.join(' || ')})"
end end

View file

@ -269,12 +269,6 @@ class ProcedureRevision < ApplicationRecord
types_de_champ_for(scope: :public).filter(&:conditionable?) types_de_champ_for(scope: :public).filter(&:conditionable?)
end 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 private
def compute_estimated_fill_duration def compute_estimated_fill_duration
@ -496,13 +490,6 @@ class ProcedureRevision < ApplicationRecord
end end
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) def replace_type_de_champ_by_clone(coordinate)
cloned_type_de_champ = coordinate.type_de_champ.deep_clone do |original, kopy| cloned_type_de_champ = coordinate.type_de_champ.deep_clone do |original, kopy|
ClonePiecesJustificativesService.clone_attachments(original, kopy) ClonePiecesJustificativesService.clone_attachments(original, kopy)

View file

@ -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| = 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 - c.with_body do
%p %p
Les dossiers répondant à vos critères dinéligibilité ne pourront pas être déposés. Plus dinformations sur linéligibilité des dossiers dans la Les dossiers répondant à vos conditions dinéligibilité ne pourront pas être déposés. Plus dinformations sur linéligibilité des dossiers dans la
= link_to('doc', ELIGIBILITE_URL, title: "Document sur linéligibilité des dossiers", **external_link_attributes) = link_to('doc', ELIGIBILITE_URL, title: "Document sur linéligibilité des dossiers", **external_link_attributes)
- if !@procedure.draft_revision.conditionable_types_de_champ.present? - if !@procedure.draft_revision.conditionable_types_de_champ.present?
%p.fr-mt-2w.fr-mb-2w %p.fr-mt-2w.fr-mb-2w
Pour configurer linéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les critères dinéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire : Pour configurer linéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les conditions dinéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire :
%ul %ul
- Logic::ChampValue::MANAGED_TYPE_DE_CHAMP.values.each do - Logic::ChampValue::MANAGED_TYPE_DE_CHAMP.values.each do
%li= "« #{t(_1, scope: [:activerecord, :attributes, :type_de_champ, :type_champs])} »" %li= "« #{t(_1, scope: [:activerecord, :attributes, :type_de_champ, :type_champs])} »"
%p.fr-mt-2w %p.fr-mt-2w
= link_to 'Ajouter un champ supportant les critères diné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 dinéligibilité', champs_admin_procedure_path(@procedure), class: 'fr-link fr-icon-arrow-right-line fr-link--icon-right'
= render Procedure::FixedFooterComponent.new(procedure: @procedure) = render Procedure::FixedFooterComponent.new(procedure: @procedure)
- else - else
= render Conditions::IneligibiliteRulesComponent.new(draft_revision: @procedure.draft_revision) = render Conditions::IneligibiliteRulesComponent.new(draft_revision: @procedure.draft_revision)

View file

@ -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 } = 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 !params.key?(:validate)
- if @ineligibilite_rules_is_computable - if @can_passer_en_construction_was && !@can_passer_en_construction_is
= 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)
= turbo_stream.append('contenu', render(Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: @dossier))) = turbo_stream.append('contenu', render(Dossiers::InvalidIneligibiliteRulesComponent.new(dossier: @dossier)))
- else @ineligibilite_rules_is_computable
= turbo_stream.remove(dom_id(@dossier, :ineligibilite_rules_broken))

View file

@ -610,7 +610,7 @@ fr:
otp_attempt: 'Code OTP (uniquement si vous avez déjà activé 2FA)' otp_attempt: 'Code OTP (uniquement si vous avez déjà activé 2FA)'
procedure: procedure:
zone: La démarche est mise en œuvre par zone: La démarche est mise en œuvre par
ineligibilite_rules: "Les règles dInéligibilité" ineligibilite_rules: "Les règles dinéligibilité"
champs: champs:
value: Valeur du champ value: Valeur du champ
default_mail_attributes: &default_mail_attributes default_mail_attributes: &default_mail_attributes

View file

@ -8,7 +8,7 @@ fr:
procedure: procedure:
hints: hints:
description: Décrivez en quelques lignes le contexte, la finalité, etc. 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é sil 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é sil y en a, les pré-requis, etc.
description_pj: Décrivez la liste des pièces jointes à fournir sil y en a description_pj: Décrivez la liste des pièces jointes à fournir sil 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" 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/'" cadre_juridique: "Exemple: 'https://www.legifrance.gouv.fr/'"

View file

@ -0,0 +1,7 @@
fr:
activerecord:
attributes:
procedure_revision:
ineligibilite_message: Message dinéligibilité
hints:
ineligibilite_message: "Ce message sera affiché à lusager si son dossier est bloqué et lui expliquera la raison de son inéligibilité."

View file

@ -10,14 +10,14 @@ RSpec.describe Dossiers::EditFooterComponent, type: :component do
let(:dossier) { create(:dossier, :brouillon) } let(:dossier) { create(:dossier, :brouillon) }
context 'when dossier can be submitted' do 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 it 'renders submit button without disabled' do
expect(subject).to have_selector('button', text: 'Déposer le dossier') expect(subject).to have_selector('button', text: 'Déposer le dossier')
end end
end end
context 'when dossier can not be submitted' do 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 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('a', text: 'Pourquoi je ne peux pas déposer mon dossier ?')
expect(subject).to have_selector('button[disabled]', text: 'Déposer le dossier') expect(subject).to have_selector('button[disabled]', text: 'Déposer le dossier')
@ -31,7 +31,7 @@ RSpec.describe Dossiers::EditFooterComponent, type: :component do
before { allow(dossier).to receive(:forked_with_changes?).and_return(true) } before { allow(dossier).to receive(:forked_with_changes?).and_return(true) }
context 'when dossier can be submitted' do 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 it 'renders submit button without disabled' do
expect(subject).to have_selector('button', text: 'Déposer les modifications') expect(subject).to have_selector('button', text: 'Déposer les modifications')
@ -39,7 +39,7 @@ RSpec.describe Dossiers::EditFooterComponent, type: :component do
end end
context 'when dossier can not be submitted' do 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 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('a', text: 'Pourquoi je ne peux pas déposer mon dossier ?')

View file

@ -19,7 +19,7 @@ describe TypesDeChampEditor::EditorComponent, type: :component do
context 'types_de_champ_private' do context 'types_de_champ_private' do
let(:is_annotation) { true } let(:is_annotation) { true }
it 'does not render public champs errors' do 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).to have_text("doit comporter au moins un choix sélectionnable")
expect(subject).not_to have_text("public") expect(subject).not_to have_text("public")
end end

View file

@ -197,14 +197,14 @@ describe Administrateurs::IneligibiliteRulesController, type: :controller do
let(:types_de_champ_public) { [] } let(:types_de_champ_public) { [] }
render_views render_views
it { expect(response.body).to have_link("Ajouter un champ supportant les critères dinéligibilité") } it { expect(response.body).to have_link("Ajouter un champ supportant les conditions dinéligibilité") }
end end
context 'rendered with tdc' do context 'rendered with tdc' do
let(:types_de_champ_public) { [{ type: :yes_no }] } let(:types_de_champ_public) { [{ type: :yes_no }] }
render_views render_views
it { expect(response.body).not_to have_link("Ajouter un champ supportant les critères dinéligibilité") } it { expect(response.body).not_to have_link("Ajouter un champ supportant les conditions dinéligibilité") }
end end
end end
end end

View file

@ -791,26 +791,27 @@ describe Users::DossiersController, type: :controller do
end end
render_views render_views
context 'when it pass from undefined to true' do context 'when it switches from true to false' do
let(:value) { must_be_greater_than + 1 } let(:value) { must_be_greater_than + 1 }
it 'raises popup' do it 'raises popup' do
subject subject
dossier.reload dossier.reload
expect(dossier.can_passer_en_construction?).to be_falsey expect(dossier.can_passer_en_construction?).to be_falsey
expect(assigns(:ineligibilite_rules_was_computable)).to eq(false) expect(assigns(:can_passer_en_construction_was)).to eq(true)
expect(assigns(:ineligibilite_rules_is_computable)).to eq(true) expect(assigns(:can_passer_en_construction_is)).to eq(false)
expect(response.body).to match(ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)) expect(response.body).to match(ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken))
end end
end end
context 'when it pass from undefined to false' do
context 'when it stays true' do
let(:value) { must_be_greater_than - 1 } let(:value) { must_be_greater_than - 1 }
it 'does nothing' do it 'does nothing' do
subject subject
dossier.reload dossier.reload
expect(dossier.can_passer_en_construction?).to be_truthy expect(dossier.can_passer_en_construction?).to be_truthy
expect(assigns(:ineligibilite_rules_was_computable)).to eq(false) expect(assigns(:can_passer_en_construction_was)).to eq(true)
expect(assigns(:ineligibilite_rules_is_computable)).to eq(true) expect(assigns(:can_passer_en_construction_is)).to eq(true)
expect(response.body).not_to have_selector("##{ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)}") expect(response.body).not_to have_selector("##{ActionView::RecordIdentifier.dom_id(dossier, :ineligibilite_rules_broken)}")
end end
end end

View file

@ -6,42 +6,6 @@ describe Logic::And do
it { expect(and_from([true, true, false]).compute).to be false } it { expect(and_from([true, true, false]).compute).to be false }
end end
describe '#computable?' do
let(:champ_1) { create(:champ_integer_number, value: value_1) }
let(:champ_2) { create(:champ_integer_number, value: value_2) }
let(:logic) do
ds_and([
greater_than(champ_value(champ_1.stable_id), constant(1)),
less_than(champ_value(champ_2.stable_id), constant(10))
])
end
subject { logic.computable?([champ_1, champ_2]) }
context "when none of champs.value are filled, and logic can't be computed" do
let(:value_1) { nil }
let(:value_2) { nil }
it { is_expected.to be_falsey }
end
context "when one champs has a value (that compute to false) the other has not, and logic keeps waiting for the 2nd value" do
let(:value_1) { 1 }
let(:value_2) { nil }
it { is_expected.to be_falsey }
end
context 'when all champs.value are filled, and logic can be computed' do
let(:value_1) { 1 }
let(:value_2) { 10 }
it { is_expected.to be_truthy }
end
context 'when one champs is not visible and the other has a value, and logic can be computed' do
let(:value_1) { 1 }
let(:value_2) { nil }
before { expect(champ_2).to receive(:visible?).and_return(false) }
it { is_expected.to be_truthy }
end
end
describe '#to_s' do describe '#to_s' do
it do it do
expect(and_from([true, false, true]).to_s([])).to eq "(Oui && Non && Oui)" expect(and_from([true, false, true]).to_s([])).to eq "(Oui && Non && Oui)"

View file

@ -28,19 +28,6 @@ describe Logic::BinaryOperator do
it { expect(greater_than(constant(2), champ_value(champ.stable_id)).sources).to eq([champ.stable_id]) } it { expect(greater_than(constant(2), champ_value(champ.stable_id)).sources).to eq([champ.stable_id]) }
it { expect(greater_than(champ_value(champ.stable_id), champ_value(champ2.stable_id)).sources).to eq([champ.stable_id, champ2.stable_id]) } it { expect(greater_than(champ_value(champ.stable_id), champ_value(champ2.stable_id)).sources).to eq([champ.stable_id, champ2.stable_id]) }
end end
describe '#computable?' do
let(:champ) { create(:champ_integer_number, value: nil) }
it 'computable?' do
expect(greater_than(champ_value(champ.stable_id), constant(1)).computable?([])).to be(false)
expect(greater_than(champ_value(champ.stable_id), constant(1)).computable?([champ])).to be(false)
allow(champ).to receive(:value).and_return(double(present?: true))
expect(greater_than(champ_value(champ.stable_id), constant(1)).computable?([champ])).to be(true)
allow(champ).to receive(:visible?).and_return(false)
expect(greater_than(champ_value(champ.stable_id), constant(1)).computable?([champ])).to be(false)
end
end
end end
describe Logic::GreaterThan do describe Logic::GreaterThan do

View file

@ -7,49 +7,6 @@ describe Logic::Or do
it { expect(or_from([false, false, false]).compute).to be false } it { expect(or_from([false, false, false]).compute).to be false }
end end
describe '#computable?' do
let(:champ_1) { create(:champ_integer_number, value: value_1) }
let(:champ_2) { create(:champ_integer_number, value: value_2) }
let(:logic) do
ds_or([
greater_than(champ_value(champ_1.stable_id), constant(1)),
less_than(champ_value(champ_2.stable_id), constant(10))
])
end
context 'with all champs' do
subject { logic.computable?([champ_1, champ_2]) }
context "when none of champs.value are filled, or logic can't be computed" do
let(:value_1) { nil }
let(:value_2) { nil }
it { is_expected.to be_falsey }
end
context "when one champs has a value (that compute to false) the other has not, or logic keeps waiting for the 2nd value" do
let(:value_1) { 1 }
let(:value_2) { nil }
it { is_expected.to be_falsey }
end
context 'when all champs.value are filled, or logic can be computed' do
let(:value_1) { 1 }
let(:value_2) { 10 }
it { is_expected.to be_truthy }
end
context 'when one champs.value and his condition is true, or logic can be computed' do
let(:value_1) { 2 }
let(:value_2) { nil }
it { is_expected.to be_truthy }
end
context 'when one champs is not visible and the other has a value that fails, or logic can be computed' do
let(:value_1) { 1 }
let(:value_2) { nil }
before { expect(champ_2).to receive(:visible?).and_return(false) }
it { is_expected.to be_truthy }
end
end
end
describe '#to_s' do describe '#to_s' do
it { expect(or_from([true, false, true]).to_s).to eq "(Oui || Non || Oui)" } it { expect(or_from([true, false, true]).to_s).to eq "(Oui || Non || Oui)" }
end end

View file

@ -9,13 +9,13 @@ describe 'Administrateurs can edit procedures', js: true do
scenario 'setup eligibilite' do scenario 'setup eligibilite' do
# explain no champ compatible # explain no champ compatible
visit admin_procedure_path(procedure) visit admin_procedure_path(procedure)
expect(page).to have_content("Champs à configurer") expect(page).to have_content("Désactivé")
# explain which champs are compatible # explain which champs are compatible
visit edit_admin_procedure_ineligibilite_rules_path(procedure) visit edit_admin_procedure_ineligibilite_rules_path(procedure)
expect(page).to have_content("Inéligibilité des dossiers") expect(page).to have_content("Inéligibilité des dossiers")
expect(page).to have_content("Pour configurer linéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les critères dinéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire : ") expect(page).to have_content("Pour configurer linéligibilité des dossiers, votre formulaire doit comporter au moins un champ supportant les conditions dinéligibilité. Il vous faut donc ajouter au moins un des champs suivant à votre formulaire : ")
click_on "Ajouter un champ supportant les critères dinéligibilité" click_on "Ajouter un champ supportant les conditions dinéligibilité"
# setup a compatible champ # setup a compatible champ
expect(page).to have_content('Champs du formulaire') expect(page).to have_content('Champs du formulaire')
@ -32,7 +32,7 @@ describe 'Administrateurs can edit procedures', js: true do
# setup rules and stuffs # setup rules and stuffs
expect(page).to have_content("Inéligibilité des dossiers") expect(page).to have_content("Inéligibilité des dossiers")
fill_in "Message dinéligibilité", with: "vous n'etes pas eligible" fill_in "Message dinéligibilité", with: "vous n'etes pas eligible"
find('label', text: 'Inéligibilité des dossiers').click find('label', text: 'Bloquer le dépôt des dossiers répondant à des conditions dinéligibilité').click
click_on "Ajouter une règle dinéligibilité" click_on "Ajouter une règle dinéligibilité"
all('select').first.select 'Un champ oui non' all('select').first.select 'Un champ oui non'
click_on 'Enregistrer' click_on 'Enregistrer'

View file

@ -9,7 +9,7 @@ describe 'Dossier Inéligibilité', js: true do
let(:published_revision) { procedure.published_revision } let(:published_revision) { procedure.published_revision }
let(:first_tdc) { published_revision.types_de_champ.first } let(:first_tdc) { published_revision.types_de_champ.first }
let(:second_tdc) { published_revision.types_de_champ.last } let(:second_tdc) { published_revision.types_de_champ.second }
let(:ineligibilite_message) { 'sry vous pouvez aps soumettre votre dossier' } let(:ineligibilite_message) { 'sry vous pouvez aps soumettre votre dossier' }
let(:eligibilite_params) { { ineligibilite_enabled: true, ineligibilite_message: } } let(:eligibilite_params) { { ineligibilite_enabled: true, ineligibilite_message: } }
@ -18,8 +18,8 @@ describe 'Dossier Inéligibilité', js: true do
login_as user, scope: :user login_as user, scope: :user
end end
context 'single condition' do describe 'ineligibilite_rules with a single BinaryOperator' do
let(:types_de_champ_public) { [{ type: :yes_no }] } let(:types_de_champ_public) { [{ type: :yes_no, stable_id: 1 }] }
let(:ineligibilite_rules) { ds_eq(champ_value(first_tdc.stable_id), constant(true)) } let(:ineligibilite_rules) { ds_eq(champ_value(first_tdc.stable_id), constant(true)) }
scenario 'can submit, can not submit, reload' do scenario 'can submit, can not submit, reload' do
@ -28,24 +28,33 @@ describe 'Dossier Inéligibilité', js: true do
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier") expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
# does raise error when dossier is filled with valid condition # does raise error when dossier is filled with condition that does not match
within "#champ-1" do
find("label", text: "Non").click find("label", text: "Non").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier") expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
# raise error when dossier is filled with invalid condition # raise error when dossier is filled with condition that matches
within "#champ-1" do
find("label", text: "Oui").click find("label", text: "Oui").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true)
expect(page).to have_content("Vous ne pouvez pas déposer votre dossier") expect(page).to have_content("Vous ne pouvez pas déposer votre dossier")
# reload page and see error because it was filled # reload page and see error
visit brouillon_dossier_path(dossier) visit brouillon_dossier_path(dossier)
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true)
expect(page).to have_content("Vous ne pouvez pas déposer votre dossier") expect(page).to have_content("Vous ne pouvez pas déposer votre dossier")
# modal is closable, and we can change our dossier response to be eligible # modal is closable, and we can change our dossier response to be eligible
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
within("#modal-eligibilite-rules-dialog") { click_on "Fermer" } within("#modal-eligibilite-rules-dialog") { click_on "Fermer" }
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
within "#champ-1" do
find("label", text: "Non").click find("label", text: "Non").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
# it works, yay # it works, yay
@ -54,7 +63,7 @@ describe 'Dossier Inéligibilité', js: true do
end end
end end
context 'or condition' do describe 'ineligibilite_rules with a Or' do
let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'] }] } let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'] }] }
let(:ineligibilite_rules) do let(:ineligibilite_rules) do
ds_or([ ds_or([
@ -69,15 +78,17 @@ describe 'Dossier Inéligibilité', js: true do
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier") expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
# only one condition is matches, cannot submit dossier and error message is clear # first condition matches (so ineligible), cannot submit dossier and error message is clear
within "#champ-#{first_tdc.stable_id}" do within "#champ-#{first_tdc.stable_id}" do
find("label", text: "Oui").click find("label", text: "Oui").click
end end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true) expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: true)
expect(page).to have_content("Vous ne pouvez pas déposer votre dossier") expect(page).to have_content("Vous ne pouvez pas déposer votre dossier")
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
within("#modal-eligibilite-rules-dialog") { click_on "Fermer" } within("#modal-eligibilite-rules-dialog") { click_on "Fermer" }
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# only one condition does not matches, I can conitnue # first condition does not matches, I can conitnue
within "#champ-#{first_tdc.stable_id}" do within "#champ-#{first_tdc.stable_id}" do
find("label", text: "Non").click find("label", text: "Non").click
end end
@ -88,12 +99,15 @@ describe 'Dossier Inéligibilité', js: true do
click_on "Accéder à votre dossier" click_on "Accéder à votre dossier"
click_on "Modifier le dossier" click_on "Modifier le dossier"
# one condition matches, means i'm blocked to send my file. # first matches, means i'm blocked to send my file.
within "#champ-#{first_tdc.stable_id}" do within "#champ-#{first_tdc.stable_id}" do
find("label", text: "Oui").click find("label", text: "Oui").click
end end
expect(page).to have_selector(:button, text: "Déposer les modifications", disabled: true) expect(page).to have_selector(:button, text: "Déposer les modifications", disabled: true)
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
within("#modal-eligibilite-rules-dialog") { click_on "Fermer" } within("#modal-eligibilite-rules-dialog") { click_on "Fermer" }
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
within "#champ-#{first_tdc.stable_id}" do within "#champ-#{first_tdc.stable_id}" do
find("label", text: "Non").click find("label", text: "Non").click
end end
@ -104,7 +118,56 @@ describe 'Dossier Inéligibilité', js: true do
find("label", text: 'Paris').click find("label", text: 'Paris').click
end end
expect(page).to have_selector(:button, text: "Déposer les modifications", disabled: true) expect(page).to have_selector(:button, text: "Déposer les modifications", disabled: true)
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
within("#modal-eligibilite-rules-dialog") { click_on "Fermer" } within("#modal-eligibilite-rules-dialog") { click_on "Fermer" }
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# none of conditions matches, i can submit
within "#champ-#{second_tdc.stable_id}" do
find("label", text: 'Marseille').click
end
# it works, yay
click_on "Déposer les modifications"
wait_until { dossier.reload.en_construction? == true }
end
end
describe 'ineligibilite_rules with a And and all visible champs' do
let(:types_de_champ_public) { [{ type: :yes_no, libelle: 'l1' }, { type: :drop_down_list, libelle: 'l2', options: ['Paris', 'Marseille'] }] }
let(:ineligibilite_rules) do
ds_and([
ds_eq(champ_value(first_tdc.stable_id), constant(true)),
ds_eq(champ_value(second_tdc.stable_id), constant('Paris'))
])
end
scenario 'can submit, can not submit, can edit, etc...' do
visit brouillon_dossier_path(dossier)
# no error while dossier is empty
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
# only one condition is matches, can submit dossier
within "#champ-#{first_tdc.stable_id}" do
find("label", text: "Oui").click
end
expect(page).to have_selector(:button, text: "Déposer le dossier", disabled: false)
expect(page).not_to have_content("Vous ne pouvez pas déposer votre dossier")
# Now test dossier modification
click_on "Déposer le dossier"
click_on "Accéder à votre dossier"
click_on "Modifier le dossier"
# second condition matches, means i'm blocked to send my file
within "#champ-#{second_tdc.stable_id}" do
find("label", text: 'Paris').click
end
expect(page).to have_selector(:button, text: "Déposer les modifications", disabled: true)
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: true)
within("#modal-eligibilite-rules-dialog") { click_on "Fermer" }
expect(page).to have_selector("#modal-eligibilite-rules-dialog", visible: false)
# none of conditions matches, i can submit # none of conditions matches, i can submit
within "#champ-#{second_tdc.stable_id}" do within "#champ-#{second_tdc.stable_id}" do

View file

@ -155,7 +155,6 @@ describe 'shared/dossiers/edit', type: :view do
let(:dossier) { create(:dossier, procedure:) } let(:dossier) { create(:dossier, procedure:) }
before do before do
allow_any_instance_of(Dossiers::InvalidIneligibiliteRulesComponent).to receive(:ineligibilite_rules_computable?).and_return(true)
allow(dossier).to receive(:can_passer_en_construction?).and_return(false) allow(dossier).to receive(:can_passer_en_construction?).and_return(false)
end end