From 5644692448e922be87c98166cb857d0df95f0cc2 Mon Sep 17 00:00:00 2001 From: mfo Date: Wed, 5 Jun 2024 17:33:03 +0200 Subject: [PATCH] feat(Logic.computable?): add computable? to know if a ineligibilite_rules set is computable --- .../concerns/champ_conditional_concern.rb | 4 ++ app/models/dossier.rb | 4 ++ app/models/logic/and.rb | 7 +++ app/models/logic/binary_operator.rb | 9 ++++ app/models/logic/or.rb | 10 +++++ app/models/procedure_revision.rb | 13 ++++++ spec/models/logic/and_spec.rb | 36 ++++++++++++++++ spec/models/logic/binary_operator_spec.rb | 15 +++++++ spec/models/logic/or_spec.rb | 43 +++++++++++++++++++ 9 files changed, 141 insertions(+) diff --git a/app/models/concerns/champ_conditional_concern.rb b/app/models/concerns/champ_conditional_concern.rb index 9e6559be9..63001229d 100644 --- a/app/models/concerns/champ_conditional_concern.rb +++ b/app/models/concerns/champ_conditional_concern.rb @@ -21,6 +21,10 @@ module ChampConditionalConcern end end + def reset_visible # recompute after a dossier update + remove_instance_variable :@visible if instance_variable_defined? :@visible + end + private def champs_for_condition diff --git a/app/models/dossier.rb b/app/models/dossier.rb index e609323bb..14d9cd4f7 100644 --- a/app/models/dossier.rb +++ b/app/models/dossier.rb @@ -938,6 +938,10 @@ class Dossier < ApplicationRecord end end + def ineligibilite_rules_computable? + revision.ineligibilite_rules_computable?(champs_for_revision(scope: :public)) + end + def demander_un_avis!(avis) log_dossier_operation(avis.claimant, :demander_un_avis, avis) end diff --git a/app/models/logic/and.rb b/app/models/logic/and.rb index 51537235f..11d31a9c0 100644 --- a/app/models/logic/and.rb +++ b/app/models/logic/and.rb @@ -7,5 +7,12 @@ class Logic::And < Logic::NAryOperator @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 diff --git a/app/models/logic/binary_operator.rb b/app/models/logic/binary_operator.rb index 812fa0605..35f6ce1a7 100644 --- a/app/models/logic/binary_operator.rb +++ b/app/models/logic/binary_operator.rb @@ -42,6 +42,15 @@ class Logic::BinaryOperator < Logic::Term 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) diff --git a/app/models/logic/or.rb b/app/models/logic/or.rb index a0e2dfeae..96a0fe133 100644 --- a/app/models/logic/or.rb +++ b/app/models/logic/or.rb @@ -7,5 +7,15 @@ class Logic::Or < Logic::NAryOperator @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 diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index a3e16f592..7e4f30860 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -269,6 +269,12 @@ class ProcedureRevision < ApplicationRecord 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 @@ -483,6 +489,13 @@ class ProcedureRevision < ApplicationRecord changes 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) diff --git a/spec/models/logic/and_spec.rb b/spec/models/logic/and_spec.rb index 67f319acb..c0eefc8e8 100644 --- a/spec/models/logic/and_spec.rb +++ b/spec/models/logic/and_spec.rb @@ -6,6 +6,42 @@ describe Logic::And do it { expect(and_from([true, true, false]).compute).to be false } 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 it do expect(and_from([true, false, true]).to_s([])).to eq "(Oui && Non && Oui)" diff --git a/spec/models/logic/binary_operator_spec.rb b/spec/models/logic/binary_operator_spec.rb index e27c3b7bc..f816e81e7 100644 --- a/spec/models/logic/binary_operator_spec.rb +++ b/spec/models/logic/binary_operator_spec.rb @@ -28,6 +28,19 @@ 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(champ_value(champ.stable_id), champ_value(champ2.stable_id)).sources).to eq([champ.stable_id, champ2.stable_id]) } 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 describe Logic::GreaterThan do @@ -43,6 +56,8 @@ end describe Logic::GreaterThanEq do include Logic + let(:champ) { create(:champ_integer_number, value: nil) } + it 'computes' do expect(greater_than_eq(constant(0), constant(1)).compute).to be(false) expect(greater_than_eq(constant(1), constant(1)).compute).to be(true) diff --git a/spec/models/logic/or_spec.rb b/spec/models/logic/or_spec.rb index 1888587d2..82d5392fb 100644 --- a/spec/models/logic/or_spec.rb +++ b/spec/models/logic/or_spec.rb @@ -7,6 +7,49 @@ describe Logic::Or do it { expect(or_from([false, false, false]).compute).to be false } 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 it { expect(or_from([true, false, true]).to_s).to eq "(Oui || Non || Oui)" } end